diff options
Diffstat (limited to 'java')
265 files changed, 6034 insertions, 4131 deletions
diff --git a/java/dagger/BUILD b/java/dagger/BUILD index 4675e3054..918340825 100644 --- a/java/dagger/BUILD +++ b/java/dagger/BUILD @@ -22,8 +22,7 @@ load( "JAVA_RELEASE_MIN", "POM_VERSION", ) -load("//tools:maven.bzl", "pom_file") -load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") +load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -38,22 +37,21 @@ java_library( ], ) -pom_file( - name = "pom", - artifact_id = "dagger", +gen_maven_artifact( + name = "artifact", + artifact_coordinates = "com.google.dagger:dagger:" + POM_VERSION, artifact_name = "Dagger", - targets = [":core"], + artifact_target = ":core", + artifact_target_maven_deps = [ + "javax.inject:javax.inject", + ], + javadoc_root_packages = ["dagger"], + javadoc_srcs = [":javadoc-srcs"], + proguard_specs = [":proguard.pro"], + r8_specs = [":r8.pro"], ) filegroup( name = "javadoc-srcs", srcs = glob(["**/*"]), ) - -javadoc_library( - name = "core-javadoc", - srcs = [":javadoc-srcs"], - exclude_packages = ["dagger.internal"], - root_packages = ["dagger"], - deps = ["//third_party/java/jsr330_inject"], -) diff --git a/java/dagger/android/BUILD b/java/dagger/android/BUILD index ef41dde4a..686e6aef5 100644 --- a/java/dagger/android/BUILD +++ b/java/dagger/android/BUILD @@ -17,14 +17,14 @@ load( "//:build_defs.bzl", - "DOCLINT_HTML_AND_SYNTAX", - "DOCLINT_REFERENCES", - "JAVA_RELEASE_MIN", "POM_VERSION", ) load("//tools:dejetify.bzl", "dejetified_library") -load("//tools:maven.bzl", "pom_file") -load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") +load( + "//tools:maven.bzl", + "gen_maven_artifact", + "pom_file", +) package(default_visibility = ["//:src"]) @@ -43,8 +43,6 @@ filegroup( android_library( name = "android", srcs = SRCS, - javacopts = JAVA_RELEASE_MIN + DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, - manifest = "AndroidManifest.xml", plugins = [ "//java/dagger/android/internal/proguard:plugin", ], @@ -60,17 +58,28 @@ android_library( ], ) -pom_file( - name = "pom", - artifact_id = "dagger-android", +gen_maven_artifact( + name = "artifact", + artifact_coordinates = "com.google.dagger:dagger-android:" + POM_VERSION, artifact_name = "Dagger Android", + artifact_target = ":android", + artifact_target_maven_deps = [ + "androidx.annotation:annotation", + "com.google.dagger:dagger", + "com.google.dagger:dagger-lint-aar", + ], + javadoc_android_api_level = 32, + javadoc_root_packages = [ + "dagger.android", + ], + javadoc_srcs = [":android-srcs"], + manifest = "AndroidManifest.xml", packaging = "aar", - targets = [":android"], ) dejetified_library( name = "dejetified-android", - input = ":android.aar", + input = ":artifact.aar", output = "android-legacy.aar", ) @@ -92,12 +101,3 @@ pom_file( packaging = "aar", targets = [":legacy-deps"], ) - -javadoc_library( - name = "android-javadoc", - srcs = [":android-srcs"], - android_api_level = 30, - exclude_packages = ["dagger.android.internal"], - root_packages = ["dagger.android"], - deps = [":android"], -) diff --git a/java/dagger/android/internal/proguard/BUILD b/java/dagger/android/internal/proguard/BUILD index 5a85279fb..7cb2cbc5d 100644 --- a/java/dagger/android/internal/proguard/BUILD +++ b/java/dagger/android/internal/proguard/BUILD @@ -22,10 +22,19 @@ package(default_visibility = ["//:src"]) java_library( name = "proguard-processor", - srcs = ["ProguardProcessor.java"], + srcs = [ + "KspProguardProcessor.java", + "ProguardProcessingStep.java", + "ProguardProcessor.java", + ], javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, deps = [ + "//java/dagger/android/processor:base_processing_step", + "//java/dagger/internal/codegen/xprocessing", "//third_party/java/auto:service", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + "@maven//:com_google_devtools_ksp_symbol_processing_api", ], ) diff --git a/java/dagger/android/internal/proguard/KspProguardProcessor.java b/java/dagger/android/internal/proguard/KspProguardProcessor.java new file mode 100644 index 000000000..e2d4c4c3b --- /dev/null +++ b/java/dagger/android/internal/proguard/KspProguardProcessor.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 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.android.internal.proguard; + +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingEnvConfig; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor; +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; +import com.google.devtools.ksp.processing.SymbolProcessor; +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment; +import com.google.devtools.ksp.processing.SymbolProcessorProvider; + +/** + * An annotation processor to generate dagger-android's specific proguard needs. This is only + * intended to run over the dagger-android project itself, as the alternative is to create an + * intermediary java_library for proguard rules to be consumed by the project. + * + * <p>Basic structure looks like this: + * + * <pre><code> + * resources/META-INF/com.android.tools/proguard/dagger-android.pro + * resources/META-INF/com.android.tools/r8/dagger-android.pro + * resources/META-INF/proguard/dagger-android.pro + * </code></pre> + */ +public final class KspProguardProcessor extends KspBasicAnnotationProcessor { + private static final XProcessingEnvConfig PROCESSING_ENV_CONFIG = + new XProcessingEnvConfig.Builder().build(); + private XProcessingEnv env; + + private KspProguardProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) { + super(symbolProcessorEnvironment, PROCESSING_ENV_CONFIG); + } + + @Override + public void initialize(XProcessingEnv env) { + this.env = env; + } + + @Override + public Iterable<XProcessingStep> processingSteps() { + return ImmutableList.of(new ProguardProcessingStep(env)); + } + + /** Provides the {@link KspProguardProcessor}. */ + @AutoService(SymbolProcessorProvider.class) + public static final class Provider implements SymbolProcessorProvider { + @Override + public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) { + return new KspProguardProcessor(symbolProcessorEnvironment); + } + } +} diff --git a/java/dagger/android/internal/proguard/ProguardProcessingStep.java b/java/dagger/android/internal/proguard/ProguardProcessingStep.java new file mode 100644 index 000000000..c4cc0612d --- /dev/null +++ b/java/dagger/android/internal/proguard/ProguardProcessingStep.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 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.android.internal.proguard; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XProcessingEnv; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.android.processor.BaseProcessingStep; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.file.Path; + +/** + * A annotation processing step to generate dagger-android's specific proguard needs. This is only + * intended to run over the dagger-android project itself, as the alternative is to create an + * intermediary java_library for proguard rules to be consumed by the project. + * + * <p>Basic structure looks like this: + * + * <pre><code> + * resources/META-INF/com.android.tools/proguard/dagger-android.pro + * resources/META-INF/com.android.tools/r8/dagger-android.pro + * resources/META-INF/proguard/dagger-android.pro + * </code></pre> + */ +public final class ProguardProcessingStep extends BaseProcessingStep { + private final XProcessingEnv processingEnv; + + ProguardProcessingStep(XProcessingEnv processingEnv) { + this.processingEnv = processingEnv; + } + + static final ClassName GENERATE_RULES_ANNOTATION_NAME = + ClassName.get("dagger.android.internal", "GenerateAndroidInjectionProguardRules"); + + @Override + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(GENERATE_RULES_ANNOTATION_NAME); + } + + @Override + public void process(XElement element, ImmutableSet<ClassName> annotationNames) { + XFiler filer = processingEnv.getFiler(); + + String errorProneRule = "-dontwarn com.google.errorprone.annotations.**\n"; + String androidInjectionKeysRule = + "-identifiernamestring class dagger.android.internal.AndroidInjectionKeys {\n" + + " java.lang.String of(java.lang.String);\n" + + "}\n"; + + writeFile(filer, "com.android.tools/proguard", errorProneRule); + writeFile(filer, "com.android.tools/r8", errorProneRule + androidInjectionKeysRule); + writeFile(filer, "proguard", errorProneRule); + } + + private void writeFile(XFiler filer, String intermediatePath, String contents) { + try (OutputStream outputStream = + filer.writeResource( + Path.of("META-INF/" + intermediatePath + "/dagger-android.pro"), + ImmutableList.<XElement>of(), + XFiler.Mode.Isolating); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) { + writer.write(contents); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/java/dagger/android/internal/proguard/ProguardProcessor.java b/java/dagger/android/internal/proguard/ProguardProcessor.java index 49274e94f..8677bf179 100644 --- a/java/dagger/android/internal/proguard/ProguardProcessor.java +++ b/java/dagger/android/internal/proguard/ProguardProcessor.java @@ -16,19 +16,13 @@ package dagger.android.internal.proguard; -import static javax.tools.StandardLocation.CLASS_OUTPUT; - +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor; import com.google.auto.service.AutoService; -import java.io.IOException; -import java.io.Writer; -import java.util.Set; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Filer; +import com.google.common.collect.ImmutableList; import javax.annotation.processing.Processor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; -import javax.lang.model.element.TypeElement; /** * An {@linkplain Processor annotation processor} to generate dagger-android's specific proguard @@ -44,46 +38,17 @@ import javax.lang.model.element.TypeElement; * </code></pre> */ @AutoService(Processor.class) -@SupportedAnnotationTypes(ProguardProcessor.GENERATE_RULES_ANNOTATION_NAME) -public final class ProguardProcessor extends AbstractProcessor { - - static final String GENERATE_RULES_ANNOTATION_NAME = - "dagger.android.internal.GenerateAndroidInjectionProguardRules"; +public final class ProguardProcessor extends JavacBasicAnnotationProcessor { + private XProcessingEnv env; @Override - public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - roundEnv - .getElementsAnnotatedWith( - processingEnv.getElementUtils().getTypeElement(GENERATE_RULES_ANNOTATION_NAME)) - .forEach(element -> generate()); - - return false; - } - - private void generate() { - Filer filer = processingEnv.getFiler(); - - String errorProneRule = "-dontwarn com.google.errorprone.annotations.**\n"; - String androidInjectionKeysRule = - "-identifiernamestring class dagger.android.internal.AndroidInjectionKeys {\n" - + " java.lang.String of(java.lang.String);\n" - + "}\n"; - - writeFile(filer, "com.android.tools/proguard", errorProneRule); - writeFile(filer, "com.android.tools/r8", errorProneRule + androidInjectionKeysRule); - writeFile(filer, "proguard", errorProneRule); + public void initialize(XProcessingEnv env) { + this.env = env; } - private static void writeFile(Filer filer, String intermediatePath, String contents) { - try (Writer writer = - filer - .createResource( - CLASS_OUTPUT, "", "META-INF/" + intermediatePath + "/dagger-android.pro") - .openWriter()) { - writer.write(contents); - } catch (IOException e) { - throw new IllegalStateException(e); - } + @Override + public Iterable<XProcessingStep> processingSteps() { + return ImmutableList.of(new ProguardProcessingStep(env)); } @Override diff --git a/java/dagger/android/processor/AndroidInjectorDescriptor.java b/java/dagger/android/processor/AndroidInjectorDescriptor.java index 12f2edf24..758d895aa 100644 --- a/java/dagger/android/processor/AndroidInjectorDescriptor.java +++ b/java/dagger/android/processor/AndroidInjectorDescriptor.java @@ -16,29 +16,23 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static dagger.android.processor.MoreDaggerElements.getAnnotatedAnnotations; -import static java.util.stream.Collectors.toList; -import static javax.lang.model.element.Modifier.ABSTRACT; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.JavaPoetExtKt; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XAnnotationValue; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.TypeName; -import java.util.List; +import dagger.internal.codegen.xprocessing.XElements; import java.util.Optional; -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor8; import javax.tools.Diagnostic.Kind; /** @@ -60,7 +54,7 @@ abstract class AndroidInjectorDescriptor { abstract ClassName enclosingModule(); /** The method annotated with {@code ContributesAndroidInjector}. */ - abstract ExecutableElement method(); + abstract XExecutableElement method(); @AutoValue.Builder abstract static class Builder { @@ -72,15 +66,15 @@ abstract class AndroidInjectorDescriptor { abstract Builder enclosingModule(ClassName enclosingModule); - abstract Builder method(ExecutableElement method); + abstract Builder method(XExecutableElement method); abstract AndroidInjectorDescriptor build(); } static final class Validator { - private final Messager messager; + private final XMessager messager; - Validator(Messager messager) { + Validator(XMessager messager) { this.messager = messager; } @@ -88,10 +82,10 @@ abstract class AndroidInjectorDescriptor { * Validates a {@code ContributesAndroidInjector} method, returning an {@link * AndroidInjectorDescriptor} if it is valid, or {@link Optional#empty()} otherwise. */ - Optional<AndroidInjectorDescriptor> createIfValid(ExecutableElement method) { + Optional<AndroidInjectorDescriptor> createIfValid(XMethodElement method) { ErrorReporter reporter = new ErrorReporter(method, messager); - if (!method.getModifiers().contains(ABSTRACT)) { + if (!method.isAbstract()) { reporter.reportError("@ContributesAndroidInjector methods must be abstract"); } @@ -101,41 +95,40 @@ abstract class AndroidInjectorDescriptor { AndroidInjectorDescriptor.Builder builder = new AutoValue_AndroidInjectorDescriptor.Builder().method(method); - TypeElement enclosingElement = MoreElements.asType(method.getEnclosingElement()); - if (!MoreDaggerElements.isAnnotationPresent(enclosingElement, TypeNames.MODULE)) { + XTypeElement enclosingElement = XElements.asTypeElement(method.getEnclosingElement()); + if (!enclosingElement.hasAnnotation(TypeNames.MODULE)) { reporter.reportError("@ContributesAndroidInjector methods must be in a @Module"); } - builder.enclosingModule(ClassName.get(enclosingElement)); + builder.enclosingModule(enclosingElement.getClassName()); - TypeMirror injectedType = method.getReturnType(); - if (MoreTypes.asDeclared(injectedType).getTypeArguments().isEmpty()) { - builder.injectedType(ClassName.get(MoreTypes.asTypeElement(injectedType))); + XType injectedType = method.getReturnType(); + if (injectedType.getTypeArguments().isEmpty()) { + builder.injectedType(injectedType.getTypeElement().getClassName()); } else { reporter.reportError( "@ContributesAndroidInjector methods cannot return parameterized types"); } - AnnotationMirror annotation = - MoreDaggerElements.getAnnotationMirror(method, TypeNames.CONTRIBUTES_ANDROID_INJECTOR) - .get(); - for (TypeMirror module : - getAnnotationValue(annotation, "modules").accept(new AllTypesVisitor(), null)) { - if (MoreDaggerElements.isAnnotationPresent(MoreTypes.asElement(module), TypeNames.MODULE)) { - builder.modulesBuilder().add((ClassName) TypeName.get(module)); + XAnnotation annotation = method.getAnnotation(TypeNames.CONTRIBUTES_ANDROID_INJECTOR); + for (XType module : getTypeList(annotation.getAnnotationValue("modules"))) { + if (module.getTypeElement().hasAnnotation(TypeNames.MODULE)) { + builder.modulesBuilder().add((ClassName) module.getTypeName()); } else { reporter.reportError(String.format("%s is not a @Module", module), annotation); } } - for (AnnotationMirror scope : Sets.union( - getAnnotatedAnnotations(method, TypeNames.SCOPE), - getAnnotatedAnnotations(method, TypeNames.SCOPE_JAVAX))) { - builder.scopesBuilder().add(AnnotationSpec.get(scope)); + for (XAnnotation scope : + Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE), + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX))) { + builder.scopesBuilder().add(JavaPoetExtKt.toAnnotationSpec(scope)); } - for (AnnotationMirror qualifier : Sets.union( - getAnnotatedAnnotations(method, TypeNames.QUALIFIER), - getAnnotatedAnnotations(method, TypeNames.QUALIFIER_JAVAX))) { + for (XAnnotation qualifier : + Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER), + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX))) { reporter.reportError( "@ContributesAndroidInjector methods cannot have qualifiers", qualifier); } @@ -143,13 +136,23 @@ abstract class AndroidInjectorDescriptor { return reporter.hasError ? Optional.empty() : Optional.of(builder.build()); } + private static ImmutableList<XType> getTypeList(XAnnotationValue annotationValue) { + if (annotationValue.hasTypeListValue()) { + return ImmutableList.copyOf(annotationValue.asTypeList()); + } + if (annotationValue.hasTypeValue()) { + return ImmutableList.of(annotationValue.asType()); + } + throw new IllegalArgumentException("Does not have type list"); + } + // TODO(ronshapiro): use ValidationReport once it is moved out of the compiler private static class ErrorReporter { - private final Element subject; - private final Messager messager; + private final XElement subject; + private final XMessager messager; private boolean hasError; - ErrorReporter(Element subject, Messager messager) { + ErrorReporter(XElement subject, XMessager messager) { this.subject = subject; this.messager = messager; } @@ -159,29 +162,10 @@ abstract class AndroidInjectorDescriptor { messager.printMessage(Kind.ERROR, error, subject); } - void reportError(String error, AnnotationMirror annotation) { + void reportError(String error, XAnnotation annotation) { hasError = true; messager.printMessage(Kind.ERROR, error, subject, annotation); } } } - - private static final class AllTypesVisitor - extends SimpleAnnotationValueVisitor8<ImmutableSet<TypeMirror>, Void> { - @Override - public ImmutableSet<TypeMirror> visitArray(List<? extends AnnotationValue> values, Void aVoid) { - return ImmutableSet.copyOf( - values.stream().flatMap(v -> v.accept(this, null).stream()).collect(toList())); - } - - @Override - public ImmutableSet<TypeMirror> visitType(TypeMirror a, Void aVoid) { - return ImmutableSet.of(a); - } - - @Override - protected ImmutableSet<TypeMirror> defaultAction(Object o, Void aVoid) { - throw new AssertionError(o); - } - } } diff --git a/java/dagger/android/processor/AndroidMapKeyProcessingStep.java b/java/dagger/android/processor/AndroidMapKeyProcessingStep.java new file mode 100644 index 000000000..8a7e83c77 --- /dev/null +++ b/java/dagger/android/processor/AndroidMapKeyProcessingStep.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2017 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.android.processor; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; +import static dagger.internal.codegen.xprocessing.XTypes.toStableString; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.internal.codegen.xprocessing.XTypes; +import javax.tools.Diagnostic.Kind; + +/** Validates the correctness of {@link dagger.MapKey}s used with {@code dagger.android}. */ +final class AndroidMapKeyProcessingStep extends BaseProcessingStep { + private final XProcessingEnv processingEnv; + + AndroidMapKeyProcessingStep(XProcessingEnv processingEnv) { + this.processingEnv = processingEnv; + } + + @Override + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.ANDROID_INJECTION_KEY, TypeNames.CLASS_KEY); + } + + @Override + public void process(XElement element, ImmutableSet<ClassName> annotationNames) { + for (ClassName annotationName : annotationNames) { + validateMethod(annotationName, XElements.asMethod(element)); + } + } + + private void validateMethod(ClassName annotation, XMethodElement method) { + if (!Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER), + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX)) + .isEmpty()) { + return; + } + + XType returnType = method.getReturnType(); + if (!factoryElement().getType().getRawType().isAssignableFrom(returnType.getRawType())) { + // if returnType is not related to AndroidInjector.Factory, ignore the method + return; + } + + if (!Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE), + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX)) + .isEmpty()) { + XAnnotation suppressedWarnings = method.getAnnotation(ClassName.get(SuppressWarnings.class)); + if (suppressedWarnings == null + || !ImmutableSet.copyOf(suppressedWarnings.getAsStringList("value")) + .contains("dagger.android.ScopedInjectorFactory")) { + XAnnotation mapKeyAnnotation = + getOnlyElement(method.getAnnotationsAnnotatedWith(TypeNames.MAP_KEY)); + XTypeElement mapKeyValueElement = + processingEnv.requireTypeElement(injectedTypeFromMapKey(mapKeyAnnotation).get()); + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "%s bindings should not be scoped. Scoping this method may leak instances of" + + " %s.", + TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName(), + mapKeyValueElement.getQualifiedName()), + method); + } + } + + validateReturnType(method); + + // @Binds methods should only have one parameter, but we can't guarantee the order of Processors + // in javac, so do a basic check for valid form + if (method.hasAnnotation(TypeNames.BINDS) && method.getParameters().size() == 1) { + validateMapKeyMatchesBindsParameter(annotation, method); + } + } + + /** Report an error if the method's return type is not {@code AndroidInjector.Factory<?>}. */ + private void validateReturnType(XMethodElement method) { + XType returnType = method.getReturnType(); + XType requiredReturnType = injectorFactoryOf(processingEnv.getWildcardType(null, null)); + + // TODO(b/311460276) use XType.isSameType when the bug is fixed. + if (!returnType.getTypeName().equals(requiredReturnType.getTypeName())) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "%s should bind %s, not %s. See https://dagger.dev/android", + method, toStableString(requiredReturnType), toStableString(returnType)), + method); + } + } + + /** + * A valid @Binds method could bind an {@code AndroidInjector.Factory} for one type, while giving + * it a map key of a different type. The return type and parameter type would pass typical @Binds + * validation, but the map lookup in {@code DispatchingAndroidInjector} would retrieve the wrong + * injector factory. + * + * <pre>{@code + * {@literal @Binds} + * {@literal @IntoMap} + * {@literal @ClassKey(GreenActivity.class)} + * abstract AndroidInjector.Factory<?> bindBlueActivity( + * BlueActivityComponent.Builder builder); + * }</pre> + */ + private void validateMapKeyMatchesBindsParameter( + ClassName annotationName, XMethodElement method) { + XType parameterType = getOnlyElement(method.getParameters()).getType(); + XAnnotation annotation = method.getAnnotation(annotationName); + XType mapKeyType = + processingEnv.requireTypeElement(injectedTypeFromMapKey(annotation).get()).getType(); + if (!XTypes.isAssignableTo(parameterType, injectorFactoryOf(mapKeyType))) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "%s does not implement AndroidInjector<%s>", + toStableString(parameterType), toStableString(mapKeyType)), + method, + annotation); + } + } + + /** Returns a {@link XType} for {@code AndroidInjector.Factory<implementationType>}. */ + private XType injectorFactoryOf(XType implementationType) { + return processingEnv.getDeclaredType(factoryElement(), implementationType); + } + + private XTypeElement factoryElement() { + return processingEnv.requireTypeElement(TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName()); + } +} diff --git a/java/dagger/android/processor/AndroidMapKeyValidator.java b/java/dagger/android/processor/AndroidMapKeyValidator.java deleted file mode 100644 index 8d674666b..000000000 --- a/java/dagger/android/processor/AndroidMapKeyValidator.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2017 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.android.processor; - -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; -import static dagger.android.processor.MoreDaggerElements.getAnnotatedAnnotations; - -import com.google.auto.common.BasicAnnotationProcessor.Step; -import com.google.auto.common.MoreElements; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.Sets; -import com.squareup.javapoet.ClassName; -import dagger.MapKey; -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic.Kind; - -/** Validates the correctness of {@link dagger.MapKey}s used with {@code dagger.android}. */ -final class AndroidMapKeyValidator implements Step { - private static final ImmutableMap<String, ClassName> SUPPORTED_ANNOTATIONS = - ImmutableMap.of( - TypeNames.ANDROID_INJECTION_KEY.toString(), TypeNames.ANDROID_INJECTION_KEY, - TypeNames.CLASS_KEY.toString(), TypeNames.CLASS_KEY); - - private final Elements elements; - private final Types types; - private final Messager messager; - - AndroidMapKeyValidator(Elements elements, Types types, Messager messager) { - this.elements = elements; - this.types = types; - this.messager = messager; - } - - @Override - public ImmutableSet<String> annotations() { - return SUPPORTED_ANNOTATIONS.keySet(); - } - - @Override - public ImmutableSet<Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation) { - ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); - elementsByAnnotation - .entries() - .forEach( - entry -> { - try { - validateMethod(entry.getKey(), MoreElements.asExecutable(entry.getValue())); - } catch (TypeNotPresentException e) { - deferredElements.add(entry.getValue()); - } - }); - return deferredElements.build(); - } - - private void validateMethod(String annotation, ExecutableElement method) { - if (!Sets.union(getAnnotatedAnnotations(method, TypeNames.QUALIFIER), - getAnnotatedAnnotations(method, TypeNames.QUALIFIER_JAVAX)).isEmpty()) { - return; - } - - TypeMirror returnType = method.getReturnType(); - if (!types.isAssignable(types.erasure(returnType), factoryElement().asType())) { - // if returnType is not related to AndroidInjector.Factory, ignore the method - return; - } - - if (!Sets.union(getAnnotatedAnnotations(method, TypeNames.SCOPE), - getAnnotatedAnnotations(method, TypeNames.SCOPE_JAVAX)).isEmpty()) { - SuppressWarnings suppressedWarnings = method.getAnnotation(SuppressWarnings.class); - if (suppressedWarnings == null - || !ImmutableSet.copyOf(suppressedWarnings.value()) - .contains("dagger.android.ScopedInjectorFactory")) { - AnnotationMirror mapKeyAnnotation = - getOnlyElement(getAnnotatedAnnotations(method, MapKey.class)); - TypeElement mapKeyValueElement = - elements.getTypeElement(injectedTypeFromMapKey(mapKeyAnnotation).get()); - messager.printMessage( - Kind.ERROR, - String.format( - "%s bindings should not be scoped. Scoping this method may leak instances of %s.", - TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName(), - mapKeyValueElement.getQualifiedName()), - method); - } - } - - validateReturnType(method); - - // @Binds methods should only have one parameter, but we can't guarantee the order of Processors - // in javac, so do a basic check for valid form - if (MoreDaggerElements.isAnnotationPresent(method, TypeNames.BINDS) - && method.getParameters().size() == 1) { - validateMapKeyMatchesBindsParameter(annotation, method); - } - } - - /** Report an error if the method's return type is not {@code AndroidInjector.Factory<?>}. */ - private void validateReturnType(ExecutableElement method) { - TypeMirror returnType = method.getReturnType(); - DeclaredType requiredReturnType = injectorFactoryOf(types.getWildcardType(null, null)); - - if (!types.isSameType(returnType, requiredReturnType)) { - messager.printMessage( - Kind.ERROR, - String.format( - "%s should bind %s, not %s. See https://dagger.dev/android", - method, requiredReturnType, returnType), - method); - } - } - - /** - * A valid @Binds method could bind an {@code AndroidInjector.Factory} for one type, while giving - * it a map key of a different type. The return type and parameter type would pass typical @Binds - * validation, but the map lookup in {@code DispatchingAndroidInjector} would retrieve the wrong - * injector factory. - * - * <pre>{@code - * {@literal @Binds} - * {@literal @IntoMap} - * {@literal @ClassKey(GreenActivity.class)} - * abstract AndroidInjector.Factory<?> bindBlueActivity( - * BlueActivityComponent.Builder builder); - * }</pre> - */ - private void validateMapKeyMatchesBindsParameter(String annotation, ExecutableElement method) { - TypeMirror parameterType = getOnlyElement(method.getParameters()).asType(); - AnnotationMirror annotationMirror = - MoreDaggerElements.getAnnotationMirror(method, SUPPORTED_ANNOTATIONS.get(annotation)).get(); - TypeMirror mapKeyType = - elements.getTypeElement(injectedTypeFromMapKey(annotationMirror).get()).asType(); - if (!types.isAssignable(parameterType, injectorFactoryOf(mapKeyType))) { - messager.printMessage( - Kind.ERROR, - String.format("%s does not implement AndroidInjector<%s>", parameterType, mapKeyType), - method, - annotationMirror); - } - } - - /** Returns a {@link DeclaredType} for {@code AndroidInjector.Factory<implementationType>}. */ - private DeclaredType injectorFactoryOf(TypeMirror implementationType) { - return types.getDeclaredType(factoryElement(), implementationType); - } - - private TypeElement factoryElement() { - return elements.getTypeElement(TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName()); - } -} diff --git a/java/dagger/android/processor/AndroidMapKeys.java b/java/dagger/android/processor/AndroidMapKeys.java index 28da2715a..e3d890e29 100644 --- a/java/dagger/android/processor/AndroidMapKeys.java +++ b/java/dagger/android/processor/AndroidMapKeys.java @@ -16,13 +16,9 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; - -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XAnnotationValue; import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; final class AndroidMapKeys { /** @@ -30,13 +26,12 @@ final class AndroidMapKeys { * it's {@link dagger.multibindings.ClassKey}, returns the fully-qualified class name of the * annotation value. Otherwise returns {@link Optional#empty()}. */ - static Optional<String> injectedTypeFromMapKey(AnnotationMirror mapKey) { - Object mapKeyClass = getAnnotationValue(mapKey, "value").getValue(); - if (mapKeyClass instanceof String) { - return Optional.of((String) mapKeyClass); - } else if (mapKeyClass instanceof TypeMirror) { - TypeElement type = MoreTypes.asTypeElement((TypeMirror) mapKeyClass); - return Optional.of(type.getQualifiedName().toString()); + static Optional<String> injectedTypeFromMapKey(XAnnotation mapKey) { + XAnnotationValue mapKeyClass = mapKey.getAnnotationValue("value"); + if (mapKeyClass.hasStringValue()) { + return Optional.of(mapKeyClass.asString()); + } else if (mapKeyClass.hasTypeValue()) { + return Optional.of(mapKeyClass.asType().getTypeElement().getQualifiedName()); } else { return Optional.empty(); } diff --git a/java/dagger/android/processor/AndroidProcessor.java b/java/dagger/android/processor/AndroidProcessor.java index 2a8ab345b..beb8d0de7 100644 --- a/java/dagger/android/processor/AndroidProcessor.java +++ b/java/dagger/android/processor/AndroidProcessor.java @@ -16,21 +16,15 @@ package dagger.android.processor; -import static javax.tools.Diagnostic.Kind.ERROR; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import com.google.auto.common.BasicAnnotationProcessor; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor; import com.google.auto.service.AutoService; -import com.google.common.base.Ascii; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import java.util.Set; -import javax.annotation.processing.Filer; -import javax.annotation.processing.Messager; import javax.annotation.processing.Processor; import javax.lang.model.SourceVersion; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; /** @@ -49,51 +43,22 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public final class AndroidProcessor extends BasicAnnotationProcessor { - private static final String FLAG_EXPERIMENTAL_USE_STRING_KEYS = - "dagger.android.experimentalUseStringKeys"; +public final class AndroidProcessor extends JavacBasicAnnotationProcessor { + private final DelegateAndroidProcessor delegate = new DelegateAndroidProcessor(); @Override - protected Iterable<? extends Step> steps() { - Filer filer = processingEnv.getFiler(); - Messager messager = processingEnv.getMessager(); - Elements elements = processingEnv.getElementUtils(); - Types types = processingEnv.getTypeUtils(); - - return ImmutableList.of( - new AndroidMapKeyValidator(elements, types, messager), - new ContributesAndroidInjectorGenerator( - new AndroidInjectorDescriptor.Validator(messager), - useStringKeys(), - filer, - elements, - processingEnv.getSourceVersion())); + public void initialize(XProcessingEnv env) { + delegate.initialize(env); } - private boolean useStringKeys() { - if (!processingEnv.getOptions().containsKey(FLAG_EXPERIMENTAL_USE_STRING_KEYS)) { - return false; - } - String flagValue = processingEnv.getOptions().get(FLAG_EXPERIMENTAL_USE_STRING_KEYS); - if (flagValue == null || Ascii.equalsIgnoreCase(flagValue, "true")) { - return true; - } else if (Ascii.equalsIgnoreCase(flagValue, "false")) { - return false; - } else { - processingEnv - .getMessager() - .printMessage( - ERROR, - String.format( - "Unknown flag value: %s. %s must be set to either 'true' or 'false'.", - flagValue, FLAG_EXPERIMENTAL_USE_STRING_KEYS)); - return false; - } + @Override + public Iterable<XProcessingStep> processingSteps() { + return delegate.processingSteps(); } @Override - public Set<String> getSupportedOptions() { - return ImmutableSet.of(FLAG_EXPERIMENTAL_USE_STRING_KEYS); + public final ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.of(DelegateAndroidProcessor.FLAG_EXPERIMENTAL_USE_STRING_KEYS); } @Override diff --git a/java/dagger/android/processor/BUILD b/java/dagger/android/processor/BUILD index d545ef8ef..f70c09102 100644 --- a/java/dagger/android/processor/BUILD +++ b/java/dagger/android/processor/BUILD @@ -22,8 +22,7 @@ load( "DOCLINT_REFERENCES", "POM_VERSION", ) -load("//tools:maven.bzl", "pom_file") -load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") +load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -33,29 +32,65 @@ filegroup( ) java_library( + name = "base_processing_step", + srcs = ["BaseProcessingStep.java"], + deps = [ + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + ], +) + +java_library( name = "processor", - srcs = [":srcs"], + srcs = glob( + ["*.java"], + exclude = ["BaseProcessingStep.java"], + ), javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, tags = ["maven_coordinates=com.google.dagger:dagger-android-processor:" + POM_VERSION], deps = [ + ":base_processing_step", "//java/dagger:core", - "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "//third_party/java/auto:common", "//third_party/java/auto:service", "//third_party/java/auto:value", "//third_party/java/guava/base", "//third_party/java/guava/collect", "//third_party/java/incap", "//third_party/java/javapoet", + "@maven//:com_google_devtools_ksp_symbol_processing_api", ], ) -pom_file( - name = "pom", - artifact_id = "dagger-android-processor", +gen_maven_artifact( + name = "artifact", + artifact_coordinates = "com.google.dagger:dagger-android-processor:" + POM_VERSION, artifact_name = "Dagger Android Processor", - targets = [":processor"], + artifact_target = ":processor", + artifact_target_libs = [ + "//java/dagger/internal/codegen/xprocessing", + "//java/dagger/android/processor:base_processing_step", + ], + artifact_target_maven_deps = [ + "com.google.dagger:dagger", + "com.google.devtools.ksp:symbol-processing-api", + "com.google.guava:guava", + "com.squareup:javapoet", + "com.google.code.findbugs:jsr305", + "com.google.dagger:dagger-spi", + "com.google.guava:failureaccess", + "com.squareup:kotlinpoet", + "net.ltgt.gradle.incap:incap", + "org.jetbrains.kotlin:kotlin-stdlib", + ], + javadoc_root_packages = [ + "dagger.android.processor", + ], + javadoc_srcs = [":srcs"], ) java_plugin( @@ -64,10 +99,3 @@ java_plugin( processor_class = "dagger.android.processor.AndroidProcessor", deps = [":processor"], ) - -javadoc_library( - name = "processor-javadoc", - srcs = [":srcs"], - root_packages = ["dagger.android.processor"], - deps = [":processor"], -) diff --git a/java/dagger/android/processor/BaseProcessingStep.java b/java/dagger/android/processor/BaseProcessingStep.java new file mode 100644 index 000000000..100ded0a0 --- /dev/null +++ b/java/dagger/android/processor/BaseProcessingStep.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 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.android.processor; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Sets.difference; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Maps; +import com.squareup.javapoet.ClassName; +import java.util.Map; +import java.util.Set; + +/** + * A {@link XProcessingStep} that processes one element at a time and defers any for which {@link + * TypeNotPresentException} is thrown. + */ +public abstract class BaseProcessingStep implements XProcessingStep { + @Override + public final ImmutableSet<String> annotations() { + return annotationClassNames().stream().map(ClassName::canonicalName).collect(toImmutableSet()); + } + + // Subclass must ensure all annotated targets are of valid type. + @Override + public ImmutableSet<XElement> process( + XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + ImmutableSet.Builder<XElement> deferredElements = ImmutableSet.builder(); + inverse(elementsByAnnotation) + .forEach( + (element, annotations) -> { + try { + process(element, annotations); + } catch (TypeNotPresentException e) { + deferredElements.add(element); + } + }); + return deferredElements.build(); + } + + /** + * Processes one element. If this method throws {@link TypeNotPresentException}, the element will + * be deferred until the next round of processing. + * + * @param annotations the subset of {@link XProcessingStep#annotations()} that annotate {@code + * element} + */ + protected abstract void process(XElement element, ImmutableSet<ClassName> annotations); + + private ImmutableMap<XElement, ImmutableSet<ClassName>> inverse( + Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + ImmutableMap<String, ClassName> annotationClassNames = + annotationClassNames().stream() + .collect(toImmutableMap(ClassName::canonicalName, className -> className)); + checkState( + annotationClassNames.keySet().containsAll(elementsByAnnotation.keySet()), + "Unexpected annotations for %s: %s", + this.getClass().getCanonicalName(), + difference(elementsByAnnotation.keySet(), annotationClassNames.keySet())); + + ImmutableSetMultimap.Builder<XElement, ClassName> builder = ImmutableSetMultimap.builder(); + elementsByAnnotation.forEach( + (annotationName, elementSet) -> + elementSet.forEach( + element -> builder.put(element, annotationClassNames.get(annotationName)))); + + return ImmutableMap.copyOf(Maps.transformValues(builder.build().asMap(), ImmutableSet::copyOf)); + } + + /** Returns the set of annotations processed by this processing step. */ + protected abstract Set<ClassName> annotationClassNames(); +} diff --git a/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java b/java/dagger/android/processor/ContributesAndroidInjectorProcessingStep.java index f3f4d18ab..9e9ed2b20 100644 --- a/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java +++ b/java/dagger/android/processor/ContributesAndroidInjectorProcessingStep.java @@ -16,23 +16,27 @@ package dagger.android.processor; -import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; +import static androidx.room.compiler.processing.JavaPoetExtKt.addOriginatingElement; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static com.squareup.javapoet.TypeSpec.interfaceBuilder; +import static dagger.android.processor.DelegateAndroidProcessor.FLAG_EXPERIMENTAL_USE_STRING_KEYS; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.methodsIn; +import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.BasicAnnotationProcessor.Step; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.base.Ascii; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; @@ -41,52 +45,26 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.WildcardTypeName; -import dagger.android.processor.AndroidInjectorDescriptor.Validator; -import java.io.IOException; -import javax.annotation.processing.Filer; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; +import dagger.internal.codegen.xprocessing.XElements; /** Generates the implementation specified in {@code ContributesAndroidInjector}. */ -final class ContributesAndroidInjectorGenerator implements Step { - +final class ContributesAndroidInjectorProcessingStep extends BaseProcessingStep { private final AndroidInjectorDescriptor.Validator validator; - private final Filer filer; - private final Elements elements; - private final boolean useStringKeys; - private final SourceVersion sourceVersion; - - ContributesAndroidInjectorGenerator( - Validator validator, - boolean useStringKeys, - Filer filer, - Elements elements, - SourceVersion sourceVersion) { - this.validator = validator; - this.useStringKeys = useStringKeys; - this.filer = filer; - this.elements = elements; - this.sourceVersion = sourceVersion; + private final XProcessingEnv processingEnv; + + ContributesAndroidInjectorProcessingStep(XProcessingEnv processingEnv) { + this.processingEnv = processingEnv; + this.validator = new AndroidInjectorDescriptor.Validator(processingEnv.getMessager()); } @Override - public ImmutableSet<String> annotations() { - return ImmutableSet.of(TypeNames.CONTRIBUTES_ANDROID_INJECTOR.toString()); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.CONTRIBUTES_ANDROID_INJECTOR); } @Override - public ImmutableSet<Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation) { - ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); - for (ExecutableElement method : methodsIn(elementsByAnnotation.values())) { - try { - validator.createIfValid(method).ifPresent(this::generate); - } catch (TypeNotPresentException e) { - deferredElements.add(method); - } - } - return deferredElements.build(); + public void process(XElement element, ImmutableSet<ClassName> annotationNames) { + validator.createIfValid(XElements.asMethod(element)).ifPresent(this::generate); } private void generate(AndroidInjectorDescriptor descriptor) { @@ -97,7 +75,7 @@ final class ContributesAndroidInjectorGenerator implements Step { .peerClass( Joiner.on('_').join(descriptor.enclosingModule().simpleNames()) + "_" - + LOWER_CAMEL.to(UPPER_CAMEL, descriptor.method().getSimpleName().toString())); + + LOWER_CAMEL.to(UPPER_CAMEL, XElements.getSimpleName(descriptor.method()))); String baseName = descriptor.injectedType().simpleName(); ClassName subcomponentName = moduleName.nestedClass(baseName + "Subcomponent"); @@ -105,7 +83,6 @@ final class ContributesAndroidInjectorGenerator implements Step { TypeSpec.Builder module = classBuilder(moduleName) - .addOriginatingElement(descriptor.method()) .addAnnotation( AnnotationSpec.builder(TypeNames.MODULE) .addMember("subcomponents", "$T.class", subcomponentName) @@ -114,16 +91,45 @@ final class ContributesAndroidInjectorGenerator implements Step { .addMethod(bindAndroidInjectorFactory(descriptor, subcomponentFactoryName)) .addType(subcomponent(descriptor, subcomponentName, subcomponentFactoryName)) .addMethod(constructorBuilder().addModifiers(PRIVATE).build()); - generatedAnnotationSpec(elements, sourceVersion, AndroidProcessor.class) - .ifPresent(module::addAnnotation); - - try { - JavaFile.builder(moduleName.packageName(), module.build()) - .skipJavaLangImports(true) - .build() - .writeTo(filer); - } catch (IOException e) { - throw new AssertionError(e); + + addOriginatingElement(module, descriptor.method()); + + XTypeElement generatedAnnotation = processingEnv.findGeneratedAnnotation(); + if (generatedAnnotation != null) { + module.addAnnotation( + AnnotationSpec.builder(generatedAnnotation.getClassName()) + .addMember( + "value", "$S", ClassName.get("dagger.android.processor", "AndroidProcessor")) + .build()); + } + + processingEnv + .getFiler() + .write( + JavaFile.builder(moduleName.packageName(), module.build()) + .skipJavaLangImports(true) + .build(), + XFiler.Mode.Isolating); + } + + private static boolean useStringKeys(XProcessingEnv processingEnv) { + if (!processingEnv.getOptions().containsKey(FLAG_EXPERIMENTAL_USE_STRING_KEYS)) { + return false; + } + String flagValue = processingEnv.getOptions().get(FLAG_EXPERIMENTAL_USE_STRING_KEYS); + if (flagValue == null || Ascii.equalsIgnoreCase(flagValue, "true")) { + return true; + } else if (Ascii.equalsIgnoreCase(flagValue, "false")) { + return false; + } else { + processingEnv + .getMessager() + .printMessage( + ERROR, + String.format( + "Unknown flag value: %s. %s must be set to either 'true' or 'false'.", + flagValue, FLAG_EXPERIMENTAL_USE_STRING_KEYS)); + return false; } } @@ -142,7 +148,7 @@ final class ContributesAndroidInjectorGenerator implements Step { } private AnnotationSpec androidInjectorMapKey(AndroidInjectorDescriptor descriptor) { - if (useStringKeys) { + if (useStringKeys(processingEnv)) { return AnnotationSpec.builder(TypeNames.ANDROID_INJECTION_KEY) .addMember("value", "$S", descriptor.injectedType().toString()) .build(); diff --git a/java/dagger/android/processor/DelegateAndroidProcessor.java b/java/dagger/android/processor/DelegateAndroidProcessor.java new file mode 100644 index 000000000..7c141f041 --- /dev/null +++ b/java/dagger/android/processor/DelegateAndroidProcessor.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 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.android.processor; + +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingEnvConfig; +import androidx.room.compiler.processing.XProcessingStep; +import com.google.common.collect.ImmutableList; +import dagger.BindsInstance; +import dagger.Component; +import javax.inject.Singleton; + +/** An implementation of Dagger Android processor that is shared between Javac and KSP. */ +final class DelegateAndroidProcessor { + static final XProcessingEnvConfig PROCESSING_ENV_CONFIG = + new XProcessingEnvConfig.Builder().build(); + static final String FLAG_EXPERIMENTAL_USE_STRING_KEYS = + "dagger.android.experimentalUseStringKeys"; + + private XProcessingEnv env; + + public void initialize(XProcessingEnv env) { + this.env = env; + } + + public ImmutableList<XProcessingStep> processingSteps() { + return ImmutableList.of( + new AndroidMapKeyProcessingStep(env), new ContributesAndroidInjectorProcessingStep(env)); + } + + @Singleton + @Component + interface Injector { + void inject(DelegateAndroidProcessor delegateAndroidProcessor); + + @Component.Factory + interface Factory { + Injector create(@BindsInstance XProcessingEnv env); + } + } +} diff --git a/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java b/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java index bcc8e5a1e..22270381c 100644 --- a/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java +++ b/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java @@ -16,34 +16,34 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XType; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; -import dagger.MapKey; -import dagger.model.Binding; -import dagger.model.BindingGraph; -import dagger.model.BindingKind; -import dagger.model.Key; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.internal.codegen.xprocessing.DaggerElements; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.internal.codegen.xprocessing.XTypes; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DaggerProcessingEnv; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; import java.util.Formatter; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; /** * Validates that the two maps that {@code DispatchingAndroidInjector} injects have logically @@ -53,6 +53,13 @@ import javax.lang.model.type.TypeMirror; */ @AutoService(BindingGraphPlugin.class) public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugin { + private DaggerProcessingEnv processingEnv; + + @Override + public void init(DaggerProcessingEnv processingEnv, Map<String, String> options) { + this.processingEnv = processingEnv; + } + @Override public void visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter) { for (Binding binding : graph.bindings()) { @@ -64,7 +71,10 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi private boolean isDispatchingAndroidInjector(Binding binding) { Key key = binding.key(); - return MoreDaggerTypes.isTypeOf(TypeNames.DISPATCHING_ANDROID_INJECTOR, key.type()) + + return XTypes.isTypeOf( + DaggerElements.toXProcessing(key.type(), processingEnv), + TypeNames.DISPATCHING_ANDROID_INJECTOR) && !key.qualifier().isPresent(); } @@ -79,7 +89,7 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi ImmutableListMultimap.Builder<String, Binding> mapKeyIndex = ImmutableListMultimap.builder(); for (Binding injectorFactory : injectorFactories) { - AnnotationMirror mapKey = mapKey(injectorFactory).get(); + XAnnotation mapKey = mapKey(injectorFactory).get(); Optional<String> injectedType = injectedTypeFromMapKey(mapKey); if (injectedType.isPresent()) { mapKeyIndex.put(injectedType.get(), injectorFactory); @@ -113,21 +123,26 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi .filter(requestedBinding -> requestedBinding.kind().equals(BindingKind.MULTIBOUND_MAP)) .filter( requestedBinding -> { - TypeMirror valueType = - MoreTypes.asDeclared(requestedBinding.key().type()).getTypeArguments().get(1); - if (!MoreDaggerTypes.isTypeOf(TypeNames.PROVIDER, valueType) - || !valueType.getKind().equals(TypeKind.DECLARED)) { + XType valueType = + DaggerElements.toXProcessing(requestedBinding.key().type(), processingEnv) + .getTypeArguments() + .get(1); + if (!XTypes.isTypeOf(valueType, TypeNames.PROVIDER) + || !XTypes.isDeclared(valueType)) { return false; } - TypeMirror providedType = MoreTypes.asDeclared(valueType).getTypeArguments().get(0); - return MoreDaggerTypes.isTypeOf(TypeNames.ANDROID_INJECTOR_FACTORY, providedType); + XType providedType = valueType.getTypeArguments().get(0); + return XTypes.isTypeOf(providedType, TypeNames.ANDROID_INJECTOR_FACTORY); }); } - private Optional<AnnotationMirror> mapKey(Binding binding) { + private Optional<XAnnotation> mapKey(Binding binding) { return binding .bindingElement() - .map(bindingElement -> getAnnotatedAnnotations(bindingElement, MapKey.class)) + .map( + bindingElement -> + XElements.getAnnotatedAnnotations( + DaggerElements.toXProcessing(bindingElement, processingEnv), TypeNames.MAP_KEY)) .flatMap( annotations -> annotations.isEmpty() diff --git a/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentValidationProcessor.java b/java/dagger/android/processor/KspAndroidProcessor.java index d46921ec2..7fe4f52d8 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentValidationProcessor.java +++ b/java/dagger/android/processor/KspAndroidProcessor.java @@ -14,36 +14,40 @@ * limitations under the License. */ -package dagger.hilt.processor.internal.definecomponent; +package dagger.android.processor; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor; import com.google.auto.service.AutoService; import com.google.devtools.ksp.processing.SymbolProcessor; import com.google.devtools.ksp.processing.SymbolProcessorEnvironment; import com.google.devtools.ksp.processing.SymbolProcessorProvider; -import dagger.hilt.processor.internal.BaseProcessingStep; -import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor; -/** - * A processor for {@link dagger.hilt.DefineComponent} and {@link - * dagger.hilt.DefineComponent.Builder}. - */ -public final class KspDefineComponentValidationProcessor extends KspBaseProcessingStepProcessor { - public KspDefineComponentValidationProcessor( - SymbolProcessorEnvironment symbolProcessorEnvironment) { - super(symbolProcessorEnvironment); +/** Ksp Processor for verifying usage of {@code dagger.android} code. */ +final class KspAndroidProcessor extends KspBasicAnnotationProcessor { + private final DelegateAndroidProcessor delegate = new DelegateAndroidProcessor(); + + private KspAndroidProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) { + super(symbolProcessorEnvironment, DelegateAndroidProcessor.PROCESSING_ENV_CONFIG); + } + + @Override + public void initialize(XProcessingEnv env) { + delegate.initialize(env); } @Override - protected BaseProcessingStep processingStep() { - return new DefineComponentValidationProcessingStep(getXProcessingEnv()); + public Iterable<XProcessingStep> processingSteps() { + return delegate.processingSteps(); } - /** Provides the {@link KspDefineComponentValidationProcessor}. */ + /** Provides the {@link KspAndroidProcessor}. */ @AutoService(SymbolProcessorProvider.class) public static final class Provider implements SymbolProcessorProvider { @Override public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) { - return new KspDefineComponentValidationProcessor(symbolProcessorEnvironment); + return new KspAndroidProcessor(symbolProcessorEnvironment); } } } diff --git a/java/dagger/android/processor/MoreDaggerElements.java b/java/dagger/android/processor/MoreDaggerElements.java deleted file mode 100644 index 572caa31a..000000000 --- a/java/dagger/android/processor/MoreDaggerElements.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2021 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.android.processor; - -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; - -import com.google.auto.common.MoreElements; -import com.google.common.collect.ImmutableSet; -import com.squareup.javapoet.ClassName; -import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; - -// TODO(bcorso): Dedupe with dagger/internal/codegen/langmodel/DaggerElements.java? -// TODO(bcorso): Contribute upstream to auto common? -/** Similar to auto common, but uses {@link ClassName} rather than {@link Class}. */ -final class MoreDaggerElements { - /** - * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain - * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of - * {@code annotationClass}. This method is a safer alternative to calling {@link - * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with - * annotation proxies. - */ - public static boolean isAnnotationPresent(Element element, ClassName annotationName) { - return getAnnotationMirror(element, annotationName).isPresent(); - } - - /** - * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on - * {@code element}, or {@link Optional#empty()} if no such annotation exists. This method is a - * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with - * annotation proxies. - */ - public static Optional<AnnotationMirror> getAnnotationMirror( - Element element, ClassName annotationName) { - String annotationClassName = annotationName.canonicalName(); - for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { - TypeElement annotationTypeElement = - MoreElements.asType(annotationMirror.getAnnotationType().asElement()); - if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { - return Optional.of(annotationMirror); - } - } - return Optional.empty(); - } - - public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations( - Element element, ClassName annotationName) { - return element.getAnnotationMirrors().stream() - .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) - .collect(toImmutableSet()); - } - - private MoreDaggerElements() {} -} diff --git a/java/dagger/android/processor/MoreDaggerTypes.java b/java/dagger/android/processor/MoreDaggerTypes.java deleted file mode 100644 index 4bde405e1..000000000 --- a/java/dagger/android/processor/MoreDaggerTypes.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2021 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.android.processor; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.auto.common.MoreElements; -import com.squareup.javapoet.ArrayTypeName; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.TypeName; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ErrorType; -import javax.lang.model.type.NoType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor8; - -// TODO(bcorso): Dedupe with dagger/internal/codegen/langmodel/DaggerTypes.java? -// TODO(bcorso): Contribute upstream to auto common? -/** Similar to auto common, but uses {@link ClassName} rather than {@link Class}. */ -final class MoreDaggerTypes { - - /** - * Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw - * type as the given {@link Class} and throws an IllegalArgumentException if the {@link - * TypeMirror} does not represent a type that can be referenced by a {@link Class} - */ - public static boolean isTypeOf(final TypeName typeName, TypeMirror type) { - checkNotNull(typeName); - return type.accept(new IsTypeOf(typeName), null); - } - - private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> { - private final TypeName typeName; - - IsTypeOf(TypeName typeName) { - this.typeName = typeName; - } - - @Override - protected Boolean defaultAction(TypeMirror type, Void ignored) { - throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); - } - - @Override - public Boolean visitNoType(NoType noType, Void p) { - if (noType.getKind().equals(TypeKind.VOID)) { - return typeName.equals(TypeName.VOID); - } - throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>."); - } - - @Override - public Boolean visitError(ErrorType errorType, Void p) { - return false; - } - - @Override - public Boolean visitPrimitive(PrimitiveType type, Void p) { - switch (type.getKind()) { - case BOOLEAN: - return typeName.equals(TypeName.BOOLEAN); - case BYTE: - return typeName.equals(TypeName.BYTE); - case CHAR: - return typeName.equals(TypeName.CHAR); - case DOUBLE: - return typeName.equals(TypeName.DOUBLE); - case FLOAT: - return typeName.equals(TypeName.FLOAT); - case INT: - return typeName.equals(TypeName.INT); - case LONG: - return typeName.equals(TypeName.LONG); - case SHORT: - return typeName.equals(TypeName.SHORT); - default: - throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); - } - } - - @Override - public Boolean visitArray(ArrayType array, Void p) { - return (typeName instanceof ArrayTypeName) - && isTypeOf(((ArrayTypeName) typeName).componentType, array.getComponentType()); - } - - @Override - public Boolean visitDeclared(DeclaredType type, Void ignored) { - TypeElement typeElement = MoreElements.asType(type.asElement()); - return (typeName instanceof ClassName) - && typeElement.getQualifiedName().contentEquals(((ClassName) typeName).canonicalName()); - } - } - - private MoreDaggerTypes() {} -} diff --git a/java/dagger/android/support/BUILD b/java/dagger/android/support/BUILD index 2fc9e2eb7..2f4c407b8 100644 --- a/java/dagger/android/support/BUILD +++ b/java/dagger/android/support/BUILD @@ -17,12 +17,14 @@ load( "//:build_defs.bzl", - "JAVA_RELEASE_MIN", "POM_VERSION", ) load("//tools:dejetify.bzl", "dejetified_library") -load("//tools:maven.bzl", "pom_file") -load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") +load( + "//tools:maven.bzl", + "gen_maven_artifact", + "pom_file", +) package(default_visibility = ["//:src"]) @@ -34,8 +36,6 @@ filegroup( android_library( name = "support", srcs = glob(["*.java"]), - javacopts = JAVA_RELEASE_MIN, - manifest = "AndroidManifest.xml", tags = ["maven_coordinates=com.google.dagger:dagger-android-support:" + POM_VERSION], deps = [ "//:dagger_with_compiler", @@ -51,17 +51,34 @@ android_library( ], ) -pom_file( - name = "pom", - artifact_id = "dagger-android-support", +gen_maven_artifact( + name = "artifact", + artifact_coordinates = "com.google.dagger:dagger-android-support:" + POM_VERSION, artifact_name = "Dagger Android Support", + artifact_target = ":support", + artifact_target_maven_deps = [ + "androidx.activity:activity", + "androidx.annotation:annotation", + "androidx.appcompat:appcompat", + "androidx.fragment:fragment", + "androidx.lifecycle:lifecycle-common", + "androidx.lifecycle:lifecycle-viewmodel", + "androidx.lifecycle:lifecycle-viewmodel-savedstate", + "com.google.dagger:dagger", + "com.google.dagger:dagger-android", + ], + javadoc_android_api_level = 32, + javadoc_root_packages = [ + "dagger.android.support", + ], + javadoc_srcs = [":support-srcs"], + manifest = "AndroidManifest.xml", packaging = "aar", - targets = [":support"], ) dejetified_library( name = "dejetified-support", - input = ":support.aar", + input = ":artifact.aar", output = "support-legacy.aar", ) @@ -85,11 +102,3 @@ pom_file( packaging = "aar", targets = [":legacy-deps"], ) - -javadoc_library( - name = "support-javadoc", - srcs = [":support-srcs"], - android_api_level = 32, - root_packages = ["dagger.android.support"], - deps = [":support"], -) diff --git a/java/dagger/hilt/android/BUILD b/java/dagger/hilt/android/BUILD index ca950a3ee..b30f6625d 100644 --- a/java/dagger/hilt/android/BUILD +++ b/java/dagger/hilt/android/BUILD @@ -15,8 +15,8 @@ # Description: # A library based on Hilt that provides standard components and automated injection for Android. load("//:build_defs.bzl", "POM_VERSION") -load("//tools:maven.bzl", "gen_maven_artifact") load("//tools:bazel_compat.bzl", "compat_kt_android_library") +load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -39,6 +39,7 @@ android_library( "//java/dagger/hilt/android/internal/managers:component_supplier", "//java/dagger/hilt/android/internal/modules", "//java/dagger/hilt/android/lifecycle:hilt_view_model", + "//java/dagger/hilt/android/lifecycle:hilt_view_model_extensions", "//java/dagger/hilt/codegen:originating_element", "//java/dagger/hilt/internal:component_entry_point", "//java/dagger/hilt/internal:component_manager", @@ -146,6 +147,14 @@ android_library( ], ) +android_library( + name = "unstable_api", + srcs = ["UnstableApi.java"], + deps = [ + "@maven//:androidx_annotation_annotation_experimental", + ], +) + java_library( name = "package_info", srcs = ["package-info.java"], @@ -163,6 +172,7 @@ android_library( ":entry_point_accessors", ":hilt_android_app", ":package_info", + ":unstable_api", "//java/dagger/hilt:artifact-core-lib", "//java/dagger/hilt/android/migration:custom_inject", "//java/dagger/hilt/android/migration:optional_inject", @@ -180,6 +190,7 @@ gen_maven_artifact( "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:unstable_api", "//java/dagger/hilt/android:early_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android:view_model_lifecycle", @@ -193,10 +204,13 @@ gen_maven_artifact( "//java/dagger/hilt/android/internal/lifecycle", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/managers:component_supplier", + "//java/dagger/hilt/android/internal/managers:saved_state_handle_holder", "//java/dagger/hilt/android/internal/migration:has_custom_inject", "//java/dagger/hilt/android/internal/migration:injected_by_hilt", "//java/dagger/hilt/android/internal/modules", + "//java/dagger/hilt/android/lifecycle:activity_retained_saved_state", "//java/dagger/hilt/android/lifecycle:hilt_view_model", + "//java/dagger/hilt/android/lifecycle:hilt_view_model_extensions", "//java/dagger/hilt/android/lifecycle:package_info", "//java/dagger/hilt/android/lifecycle:retained_lifecycle", "//java/dagger/hilt/android/migration:custom_inject", @@ -216,6 +230,7 @@ gen_maven_artifact( artifact_target_maven_deps = [ "androidx.activity:activity", "androidx.annotation:annotation", + "androidx.annotation:annotation-experimental", "androidx.fragment:fragment", "androidx.lifecycle:lifecycle-common", "androidx.lifecycle:lifecycle-viewmodel", @@ -244,10 +259,9 @@ gen_maven_artifact( ], manifest = "AndroidManifest.xml", packaging = "aar", - proguard_specs = [ + proguard_and_r8_specs = [ "//java/dagger/hilt:proguard-rules.pro", - ":proguard-rules.pro", - "//java/dagger/hilt/android/lifecycle:proguard-rules.pro", + "//java/dagger/hilt/android:proguard-rules.pro", "//java/dagger/hilt/internal:proguard-rules.pro", ], ) diff --git a/java/dagger/hilt/android/UnstableApi.java b/java/dagger/hilt/android/UnstableApi.java new file mode 100644 index 000000000..dd328ecb3 --- /dev/null +++ b/java/dagger/hilt/android/UnstableApi.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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.hilt.android; + +import androidx.annotation.RequiresOptIn; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Mark unstable Api usage. */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +public @interface UnstableApi {} diff --git a/java/dagger/hilt/android/internal/BUILD b/java/dagger/hilt/android/internal/BUILD index ae8a06692..4a231a85e 100644 --- a/java/dagger/hilt/android/internal/BUILD +++ b/java/dagger/hilt/android/internal/BUILD @@ -21,6 +21,7 @@ android_library( name = "internal", srcs = [ "Contexts.java", + "OnReceiveBytecodeInjectionMarker.java", "ThreadUtil.java", ], ) diff --git a/java/dagger/hilt/android/internal/OnReceiveBytecodeInjectionMarker.java b/java/dagger/hilt/android/internal/OnReceiveBytecodeInjectionMarker.java new file mode 100644 index 000000000..ed5abbe4a --- /dev/null +++ b/java/dagger/hilt/android/internal/OnReceiveBytecodeInjectionMarker.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.hilt.android.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The marker annotation used to denote that we need to inject super.onReceive() call + * to the @AndroidEntryPoint-annotated BroadcastReceiver's onReceive() method. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface OnReceiveBytecodeInjectionMarker { } + diff --git a/java/dagger/hilt/android/internal/builders/ActivityRetainedComponentBuilder.java b/java/dagger/hilt/android/internal/builders/ActivityRetainedComponentBuilder.java index 110b3fef1..ff5998c01 100644 --- a/java/dagger/hilt/android/internal/builders/ActivityRetainedComponentBuilder.java +++ b/java/dagger/hilt/android/internal/builders/ActivityRetainedComponentBuilder.java @@ -16,11 +16,17 @@ package dagger.hilt.android.internal.builders; +import dagger.BindsInstance; import dagger.hilt.DefineComponent; import dagger.hilt.android.components.ActivityRetainedComponent; +import dagger.hilt.android.internal.managers.SavedStateHandleHolder; /** Interface for creating a {@link ActivityRetainedComponent}. */ @DefineComponent.Builder public interface ActivityRetainedComponentBuilder { + + ActivityRetainedComponentBuilder savedStateHandleHolder( + @BindsInstance SavedStateHandleHolder savedStateHandleHolder); + ActivityRetainedComponent build(); } diff --git a/java/dagger/hilt/android/internal/builders/BUILD b/java/dagger/hilt/android/internal/builders/BUILD index 598503eff..7f26e1dce 100644 --- a/java/dagger/hilt/android/internal/builders/BUILD +++ b/java/dagger/hilt/android/internal/builders/BUILD @@ -25,6 +25,7 @@ android_library( "//java/dagger/hilt:define_component", "//java/dagger/hilt/android:view_model_lifecycle", "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/internal/managers:saved_state_handle_holder", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", diff --git a/java/dagger/hilt/android/internal/lifecycle/BUILD b/java/dagger/hilt/android/internal/lifecycle/BUILD index f7314d279..7afd1dd9d 100644 --- a/java/dagger/hilt/android/internal/lifecycle/BUILD +++ b/java/dagger/hilt/android/internal/lifecycle/BUILD @@ -38,6 +38,7 @@ android_library( "@maven//:androidx_lifecycle_lifecycle_viewmodel", "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", "@maven//:androidx_savedstate_savedstate", + "@maven//:org_jetbrains_kotlin_kotlin_stdlib", ], ) diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java index 78b8eb16b..67e68e71a 100644 --- a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java +++ b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java @@ -29,7 +29,7 @@ import dagger.hilt.android.components.ActivityComponent; import dagger.hilt.android.components.FragmentComponent; import dagger.hilt.android.internal.builders.ViewModelComponentBuilder; import dagger.multibindings.Multibinds; -import java.util.Set; +import java.util.Map; import javax.inject.Inject; /** @@ -69,12 +69,12 @@ public final class DefaultViewModelFactories { /** Internal factory for the Hilt ViewModel Factory. */ public static final class InternalFactoryFactory { - private final Set<String> keySet; + private final Map<Class<?>, Boolean> keySet; private final ViewModelComponentBuilder viewModelComponentBuilder; @Inject InternalFactoryFactory( - @HiltViewModelMap.KeySet Set<String> keySet, + @HiltViewModelMap.KeySet Map<Class<?>, Boolean> keySet, ViewModelComponentBuilder viewModelComponentBuilder) { this.keySet = keySet; this.viewModelComponentBuilder = viewModelComponentBuilder; @@ -103,7 +103,7 @@ public final class DefaultViewModelFactories { interface ActivityModule { @Multibinds @HiltViewModelMap.KeySet - abstract Set<String> viewModelKeys(); + abstract Map<Class<?>, Boolean> viewModelKeys(); } /** The activity entry point to retrieve the factory. */ diff --git a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelAssistedMap.java b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelAssistedMap.java new file mode 100644 index 000000000..69bb2b118 --- /dev/null +++ b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelAssistedMap.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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.hilt.android.internal.lifecycle; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** + * Internal qualifier for the multibinding map of assisted factories for @AssistedInject-annotated + * ViewModels used by the {@link dagger.hilt.android.lifecycle.HiltViewModelFactory}. + */ +@Qualifier +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.PARAMETER}) +public @interface HiltViewModelAssistedMap {} diff --git a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java index 52f31b920..6819c24bd 100644 --- a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java +++ b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java @@ -16,12 +16,12 @@ package dagger.hilt.android.internal.lifecycle; +import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle; + import android.app.Activity; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.AbstractSavedStateViewModelFactory; -import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.viewmodel.CreationExtras; @@ -35,8 +35,8 @@ import dagger.hilt.android.components.ViewModelComponent; import dagger.hilt.android.internal.builders.ViewModelComponentBuilder; import dagger.multibindings.Multibinds; import java.util.Map; -import java.util.Set; import javax.inject.Provider; +import kotlin.jvm.functions.Function1; /** * View Model Provider Factory for the Hilt Extension. @@ -54,54 +54,110 @@ public final class HiltViewModelFactory implements ViewModelProvider.Factory { @InstallIn(ViewModelComponent.class) public interface ViewModelFactoriesEntryPoint { @HiltViewModelMap - Map<String, Provider<ViewModel>> getHiltViewModelMap(); + Map<Class<?>, Provider<ViewModel>> getHiltViewModelMap(); + + // From ViewModel class names to user defined @AssistedFactory-annotated implementations. + @HiltViewModelAssistedMap + Map<Class<?>, Object> getHiltViewModelAssistedMap(); } + /** Creation extra key for the callbacks that create @AssistedInject-annotated ViewModels. */ + public static final CreationExtras.Key<Function1<Object, ViewModel>> CREATION_CALLBACK_KEY = + new CreationExtras.Key<Function1<Object, ViewModel>>() {}; + /** Hilt module for providing the empty multi-binding map of ViewModels. */ @Module @InstallIn(ViewModelComponent.class) interface ViewModelModule { @Multibinds @HiltViewModelMap - Map<String, ViewModel> hiltViewModelMap(); + Map<Class<?>, ViewModel> hiltViewModelMap(); + + @Multibinds + @HiltViewModelAssistedMap + Map<Class<?>, Object> hiltViewModelAssistedMap(); } - private final Set<String> hiltViewModelKeys; + private final Map<Class<?>, Boolean> hiltViewModelKeys; private final ViewModelProvider.Factory delegateFactory; - private final AbstractSavedStateViewModelFactory hiltViewModelFactory; + private final ViewModelProvider.Factory hiltViewModelFactory; public HiltViewModelFactory( - @NonNull Set<String> hiltViewModelKeys, + @NonNull Map<Class<?>, Boolean> hiltViewModelKeys, @NonNull ViewModelProvider.Factory delegateFactory, @NonNull ViewModelComponentBuilder viewModelComponentBuilder) { this.hiltViewModelKeys = hiltViewModelKeys; this.delegateFactory = delegateFactory; this.hiltViewModelFactory = - new AbstractSavedStateViewModelFactory() { + new ViewModelProvider.Factory() { @NonNull @Override - @SuppressWarnings("unchecked") - protected <T extends ViewModel> T create( - @NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle) { + public <T extends ViewModel> T create( + @NonNull Class<T> modelClass, @NonNull CreationExtras extras) { RetainedLifecycleImpl lifecycle = new RetainedLifecycleImpl(); - ViewModelComponent component = viewModelComponentBuilder - .savedStateHandle(handle) - .viewModelLifecycle(lifecycle) - .build(); + ViewModelComponent component = + viewModelComponentBuilder + .savedStateHandle(createSavedStateHandle(extras)) + .viewModelLifecycle(lifecycle) + .build(); + T viewModel = createViewModel(component, modelClass, extras); + viewModel.addCloseable(lifecycle::dispatchOnCleared); + return viewModel; + } + + private <T extends ViewModel> T createViewModel( + @NonNull ViewModelComponent component, + @NonNull Class<T> modelClass, + @NonNull CreationExtras extras) { Provider<? extends ViewModel> provider = EntryPoints.get(component, ViewModelFactoriesEntryPoint.class) .getHiltViewModelMap() - .get(modelClass.getName()); - if (provider == null) { - throw new IllegalStateException( - "Expected the @HiltViewModel-annotated class '" - + modelClass.getName() - + "' to be available in the multi-binding of " - + "@HiltViewModelMap but none was found."); + .get(modelClass); + Function1<Object, ViewModel> creationCallback = extras.get(CREATION_CALLBACK_KEY); + Object assistedFactory = + EntryPoints.get(component, ViewModelFactoriesEntryPoint.class) + .getHiltViewModelAssistedMap() + .get(modelClass); + + if (assistedFactory == null) { + if (creationCallback == null) { + if (provider == null) { + throw new IllegalStateException( + "Expected the @HiltViewModel-annotated class " + + modelClass.getName() + + " to be available in the multi-binding of " + + "@HiltViewModelMap" + + " but none was found."); + } else { + return (T) provider.get(); + } + } else { + // Provider could be null or non-null. + throw new IllegalStateException( + "Found creation callback but class " + + modelClass.getName() + + " does not have an assisted factory specified in @HiltViewModel."); + } + } else { + if (provider == null) { + if (creationCallback == null) { + throw new IllegalStateException( + "Found @HiltViewModel-annotated class " + + modelClass.getName() + + " using @AssistedInject but no creation callback" + + " was provided in CreationExtras."); + } else { + return (T) creationCallback.invoke(assistedFactory); + } + } else { + // Creation callback could be null or non-null. + throw new AssertionError( + "Found the @HiltViewModel-annotated class " + + modelClass.getName() + + " in both the multi-bindings of " + + "@HiltViewModelMap and @HiltViewModelAssistedMap."); + } } - ViewModel viewModel = provider.get(); - viewModel.addCloseable(lifecycle::dispatchOnCleared); - return (T) viewModel; } }; } @@ -110,7 +166,7 @@ public final class HiltViewModelFactory implements ViewModelProvider.Factory { @Override public <T extends ViewModel> T create( @NonNull Class<T> modelClass, @NonNull CreationExtras extras) { - if (hiltViewModelKeys.contains(modelClass.getName())) { + if (hiltViewModelKeys.containsKey(modelClass)) { return hiltViewModelFactory.create(modelClass, extras); } else { return delegateFactory.create(modelClass, extras); @@ -120,7 +176,7 @@ public final class HiltViewModelFactory implements ViewModelProvider.Factory { @NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { - if (hiltViewModelKeys.contains(modelClass.getName())) { + if (hiltViewModelKeys.containsKey(modelClass)) { return hiltViewModelFactory.create(modelClass); } else { return delegateFactory.create(modelClass); @@ -131,7 +187,8 @@ public final class HiltViewModelFactory implements ViewModelProvider.Factory { @InstallIn(ActivityComponent.class) interface ActivityCreatorEntryPoint { @HiltViewModelMap.KeySet - Set<String> getViewModelKeys(); + Map<Class<?>, Boolean> getViewModelKeys(); + ViewModelComponentBuilder getViewModelComponentBuilder(); } diff --git a/java/dagger/hilt/android/internal/managers/ActivityComponentManager.java b/java/dagger/hilt/android/internal/managers/ActivityComponentManager.java index 50e4ce11d..3fa4910d0 100644 --- a/java/dagger/hilt/android/internal/managers/ActivityComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ActivityComponentManager.java @@ -70,6 +70,12 @@ public class ActivityComponentManager implements GeneratedComponentManager<Objec return component; } + public final SavedStateHandleHolder getSavedStateHandleHolder() { + // This will only be used on base activity that extends ComponentActivity. + return ((ActivityRetainedComponentManager) activityRetainedComponentManager) + .getSavedStateHandleHolder(); + } + protected Object createComponent() { if (!(activity.getApplication() instanceof GeneratedComponentManager)) { throw new IllegalStateException( diff --git a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java index df63bd3d1..dc3539c34 100644 --- a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java @@ -23,6 +23,7 @@ import androidx.annotation.Nullable; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; +import androidx.lifecycle.viewmodel.CreationExtras; import dagger.Module; import dagger.Provides; import dagger.hilt.EntryPoint; @@ -57,15 +58,22 @@ final class ActivityRetainedComponentManager static final class ActivityRetainedComponentViewModel extends ViewModel { private final ActivityRetainedComponent component; + private final SavedStateHandleHolder savedStateHandleHolder; - ActivityRetainedComponentViewModel(ActivityRetainedComponent component) { + ActivityRetainedComponentViewModel( + ActivityRetainedComponent component, SavedStateHandleHolder savedStateHandleHolder) { this.component = component; + this.savedStateHandleHolder = savedStateHandleHolder; } ActivityRetainedComponent getComponent() { return component; } + SavedStateHandleHolder getSavedStateHandleHolder() { + return savedStateHandleHolder; + } + @Override protected void onCleared() { super.onCleared(); @@ -95,13 +103,17 @@ final class ActivityRetainedComponentManager @NonNull @Override @SuppressWarnings("unchecked") - public <T extends ViewModel> T create(@NonNull Class<T> aClass) { + public <T extends ViewModel> T create( + @NonNull Class<T> aClass, CreationExtras creationExtras) { + SavedStateHandleHolder savedStateHandleHolder = + new SavedStateHandleHolder(creationExtras); ActivityRetainedComponent component = EntryPointAccessors.fromApplication( - context, ActivityRetainedComponentBuilderEntryPoint.class) + context, ActivityRetainedComponentBuilderEntryPoint.class) .retainedComponentBuilder() + .savedStateHandleHolder(savedStateHandleHolder) .build(); - return (T) new ActivityRetainedComponentViewModel(component); + return (T) new ActivityRetainedComponentViewModel(component, savedStateHandleHolder); } }); } @@ -118,6 +130,12 @@ final class ActivityRetainedComponentManager return component; } + public SavedStateHandleHolder getSavedStateHandleHolder() { + return getViewModelProvider(viewModelStoreOwner, context) + .get(ActivityRetainedComponentViewModel.class) + .getSavedStateHandleHolder(); + } + private ActivityRetainedComponent createComponent() { return getViewModelProvider(viewModelStoreOwner, context) .get(ActivityRetainedComponentViewModel.class) diff --git a/java/dagger/hilt/android/internal/managers/BUILD b/java/dagger/hilt/android/internal/managers/BUILD index e553c6e8c..950b51129 100644 --- a/java/dagger/hilt/android/internal/managers/BUILD +++ b/java/dagger/hilt/android/internal/managers/BUILD @@ -30,25 +30,46 @@ android_library( "ApplicationComponentManager.java", "BroadcastReceiverComponentManager.java", "FragmentComponentManager.java", + "SavedStateHandleModule.java", "ServiceComponentManager.java", "ViewComponentManager.java", ], + exports = [":saved_state_handle_holder"], deps = [ ":component_supplier", + ":saved_state_handle_holder", "//:dagger_with_compiler", "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android:entry_point_accessors", + "//java/dagger/hilt/android:unstable_api", "//java/dagger/hilt/android/components", "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/internal/lifecycle", + "//java/dagger/hilt/android/lifecycle:activity_retained_saved_state", "//java/dagger/hilt/android/scopes", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", + "@maven//:androidx_annotation_annotation_experimental", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + ], +) + +android_library( + name = "saved_state_handle_holder", + srcs = ["SavedStateHandleHolder.java"], + deps = [ + "//java/dagger/hilt/android/internal", + "//java/dagger/hilt/internal:preconditions", + "@maven//:androidx_activity_activity", + "@maven//:androidx_annotation_annotation", "@maven//:androidx_fragment_fragment", "@maven//:androidx_lifecycle_lifecycle_common", "@maven//:androidx_lifecycle_lifecycle_viewmodel", diff --git a/java/dagger/hilt/android/internal/managers/SavedStateHandleHolder.java b/java/dagger/hilt/android/internal/managers/SavedStateHandleHolder.java new file mode 100644 index 000000000..835015900 --- /dev/null +++ b/java/dagger/hilt/android/internal/managers/SavedStateHandleHolder.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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.hilt.android.internal.managers; + +import static dagger.hilt.internal.Preconditions.checkNotNull; +import static dagger.hilt.internal.Preconditions.checkState; + +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.SavedStateHandleSupport; +import androidx.lifecycle.viewmodel.CreationExtras; +import androidx.lifecycle.viewmodel.MutableCreationExtras; +import dagger.hilt.android.internal.ThreadUtil; + +/** Implementation for SavedStateHandleHolder. */ +public final class SavedStateHandleHolder { + private CreationExtras extras; + private SavedStateHandle handle; + private final boolean nonComponentActivity; + + SavedStateHandleHolder(@Nullable CreationExtras extras) { + nonComponentActivity = (extras == null); + this.extras = extras; + } + + SavedStateHandle getSavedStateHandle() { + ThreadUtil.ensureMainThread(); + checkState( + !nonComponentActivity, + "Activity that does not extend ComponentActivity cannot use SavedStateHandle"); + if (handle != null) { + return handle; + } + checkNotNull( + extras, + "The first access to SavedStateHandle should happen between super.onCreate() and" + + " super.onDestroy()"); + // Clean up default args, since those are unused and we don't want to duplicate those for each + // SavedStateHandle + MutableCreationExtras mutableExtras = new MutableCreationExtras(extras); + mutableExtras.set(SavedStateHandleSupport.DEFAULT_ARGS_KEY, Bundle.EMPTY); + extras = mutableExtras; + handle = SavedStateHandleSupport.createSavedStateHandle(extras); + + extras = null; + return handle; + } + + public void clear() { + extras = null; + } + + public void setExtras(CreationExtras extras) { + if (handle != null) { + // If handle is already created, we don't need to store CreationExtras. + return; + } + this.extras = extras; + } + + public boolean isInvalid() { + return handle == null && extras == null; + } +} diff --git a/java/dagger/hilt/android/internal/managers/SavedStateHandleModule.java b/java/dagger/hilt/android/internal/managers/SavedStateHandleModule.java new file mode 100644 index 000000000..18ca508bc --- /dev/null +++ b/java/dagger/hilt/android/internal/managers/SavedStateHandleModule.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 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.hilt.android.internal.managers; + +import androidx.annotation.OptIn; +import androidx.lifecycle.SavedStateHandle; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.UnstableApi; +import dagger.hilt.android.components.ActivityRetainedComponent; +import dagger.hilt.android.lifecycle.ActivityRetainedSavedState; +import dagger.hilt.android.scopes.ActivityRetainedScoped; + +/** Module providing SavedStateHandle from ActivityRetainedComponent. */ +@Module +@InstallIn(ActivityRetainedComponent.class) +abstract class SavedStateHandleModule { + @OptIn(markerClass = UnstableApi.class) + @ActivityRetainedSavedState + @ActivityRetainedScoped + @Provides + static SavedStateHandle provideSavedStateHandle(SavedStateHandleHolder savedStateHandleHolder) { + return savedStateHandleHolder.getSavedStateHandle(); + } +} diff --git a/java/dagger/hilt/android/lifecycle/ActivityRetainedSavedState.java b/java/dagger/hilt/android/lifecycle/ActivityRetainedSavedState.java new file mode 100644 index 000000000..629843538 --- /dev/null +++ b/java/dagger/hilt/android/lifecycle/ActivityRetainedSavedState.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.hilt.android.lifecycle; + +import dagger.hilt.android.UnstableApi; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** Qualifies a binding that belongs to ActivityRetainedComponent. */ +@Qualifier +@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) +@UnstableApi +@Retention(RetentionPolicy.CLASS) +public @interface ActivityRetainedSavedState {} diff --git a/java/dagger/hilt/android/lifecycle/BUILD b/java/dagger/hilt/android/lifecycle/BUILD index 456dbe76a..26394f0f8 100644 --- a/java/dagger/hilt/android/lifecycle/BUILD +++ b/java/dagger/hilt/android/lifecycle/BUILD @@ -15,6 +15,8 @@ # Description: # Hilt ViewModel integration. +load("//tools:bazel_compat.bzl", "compat_kt_android_library") + package(default_visibility = ["//:src"]) java_library( @@ -31,7 +33,6 @@ android_library( exported_plugins = [ "//java/dagger/hilt/android/processor/internal/viewmodel:processor", ], - proguard_specs = ["proguard-rules.pro"], exports = [ "//:dagger_with_compiler", "//java/dagger/hilt:install_in", @@ -54,6 +55,26 @@ android_library( ], ) +android_library( + name = "activity_retained_saved_state", + srcs = ["ActivityRetainedSavedState.java"], + deps = [ + "//java/dagger/hilt/android:unstable_api", + "//third_party/java/jsr330_inject", + ], +) + +compat_kt_android_library( + name = "hilt_view_model_extensions", + srcs = ["HiltViewModelExtensions.kt"], + deps = [ + ":package_info", + "//java/dagger/hilt/android/internal/lifecycle", + "@maven//:androidx_annotation_annotation", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + ], +) + filegroup( name = "srcs_filegroup", srcs = glob(["*"]), diff --git a/java/dagger/hilt/android/lifecycle/HiltViewModel.java b/java/dagger/hilt/android/lifecycle/HiltViewModel.java index 198ec8abe..a5e486f21 100644 --- a/java/dagger/hilt/android/lifecycle/HiltViewModel.java +++ b/java/dagger/hilt/android/lifecycle/HiltViewModel.java @@ -53,7 +53,46 @@ import java.lang.annotation.Target; * } * </pre> * - * <p>Exactly one constructor in the {@code ViewModel} must be annotated with {@code Inject}. + * <p>{@code ViewModel}s annotated with {@link HiltViewModel} can also be used with assisted + * injection: + * + * <pre> + * @HiltViewModel(assistedFactory = DonutViewModel.Factory.class) + * public class DonutViewModel extends ViewModel { + * @AssistedInject + * public DonutViewModel( + * SavedStateHandle handle, + * RecipeRepository repository, + * $#64;Assisted int donutId + * ) { + * // ... + * } + * + * @AssistedFactory + * public interface Factory { + * DonutViewModel create(int donutId); + * } + * } + * </pre> + * + * <pre> + * @AndroidEntryPoint + * public class CookingActivity extends AppCompatActivity { + * public void onCreate(Bundle savedInstanceState) { + * DonutViewModel vm = new ViewModelProvider( + * getViewModelStore(), + * getDefaultViewModelProviderFactory(), + * HiltViewModelExtensions.withCreationCallback( + * getDefaultViewModelCreationExtras(), + * (DonutViewModel.Factory factory) -> factory.create(1) + * ) + * ).get(DonutViewModel.class); + * } + * } + * </pre> + * + * <p>Exactly one constructor in the {@code ViewModel} must be annotated with {@code Inject} or + * {@code AssistedInject}. * * <p>Only dependencies available in the {@link dagger.hilt.android.components.ViewModelComponent} * can be injected into the {@code ViewModel}. @@ -65,4 +104,11 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) @GeneratesRootInput -public @interface HiltViewModel {} +public @interface HiltViewModel { + /** + * Returns a factory class that can be used to create this ViewModel with assisted injection. The + * default value `Object.class` denotes that no factory is specified and the ViewModel is not + * assisted injected. + */ + Class<?> assistedFactory() default Object.class; +} diff --git a/java/dagger/hilt/android/lifecycle/HiltViewModelExtensions.kt b/java/dagger/hilt/android/lifecycle/HiltViewModelExtensions.kt new file mode 100644 index 000000000..f212c58ba --- /dev/null +++ b/java/dagger/hilt/android/lifecycle/HiltViewModelExtensions.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 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. + */ + +@file:JvmName("HiltViewModelExtensions") + +package dagger.hilt.android.lifecycle + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.MutableCreationExtras +import dagger.hilt.android.internal.lifecycle.HiltViewModelFactory + +/** + * Returns a new {@code CreationExtras} with the original entries plus the passed in creation + * callback. The callback is used by Hilt to create {@link AssistedInject}-annotated {@link + * HiltViewModel}s. + * + * @param callback A creation callback that takes an assisted factory and returns a {@code + * ViewModel}. + */ +fun <VMF> CreationExtras.withCreationCallback(callback: (VMF) -> ViewModel): CreationExtras = + MutableCreationExtras(this).addCreationCallback(callback) + +/** + * Returns the {@code MutableCreationExtras} with the passed in creation callback added. The + * callback is used by Hilt to create {@link AssistedInject}-annotated {@link HiltViewModel}s. + * + * @param callback A creation callback that takes an assisted factory and returns a {@code + * ViewModel}. + */ +@Suppress("UNCHECKED_CAST") +fun <VMF> MutableCreationExtras.addCreationCallback(callback: (VMF) -> ViewModel): CreationExtras = + this.apply { + this[HiltViewModelFactory.CREATION_CALLBACK_KEY] = { factory -> callback(factory as VMF) } + } diff --git a/java/dagger/hilt/android/lifecycle/proguard-rules.pro b/java/dagger/hilt/android/lifecycle/proguard-rules.pro deleted file mode 100644 index 6c647f105..000000000 --- a/java/dagger/hilt/android/lifecycle/proguard-rules.pro +++ /dev/null @@ -1,2 +0,0 @@ -# Keep class names of Hilt injected ViewModels since their name are used as a multibinding map key. --keepnames @dagger.hilt.android.lifecycle.HiltViewModel class * extends androidx.lifecycle.ViewModel
\ No newline at end of file diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle index 3c0349565..5509261c5 100644 --- a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle +++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle @@ -2,8 +2,10 @@ plugins { id 'org.jetbrains.kotlin.jvm' } -kotlin { - jvmToolchain(11) +compileKotlin { + kotlinOptions { + jvmTarget = 11 + } } dependencies { diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle index 5bc8d3d1c..982949a49 100644 --- a/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle +++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle @@ -2,8 +2,10 @@ plugins { id 'org.jetbrains.kotlin.jvm' } -kotlin { - jvmToolchain(11) +compileKotlin { + kotlinOptions { + jvmTarget = 11 + } } dependencies { diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle index a41330196..e0331c2a8 100644 --- a/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle +++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle @@ -2,8 +2,10 @@ plugins { id 'org.jetbrains.kotlin.jvm' } -kotlin { - jvmToolchain(11) +compileKotlin { + kotlinOptions { + jvmTarget = 11 + } } dependencies { diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle index 3ad558a8a..f270efb9f 100644 --- a/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle +++ b/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle @@ -2,8 +2,10 @@ plugins { id 'org.jetbrains.kotlin.jvm' } -kotlin { - jvmToolchain(11) +compileKotlin { + kotlinOptions { + jvmTarget = 11 + } } dependencies { diff --git a/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle index 71ea72424..d8238e8f1 100644 --- a/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle +++ b/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle @@ -2,8 +2,10 @@ plugins { id 'org.jetbrains.kotlin.jvm' } -kotlin { - jvmToolchain(11) +compileKotlin { + kotlinOptions { + jvmTarget = 11 + } } dependencies { diff --git a/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt b/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt index acc59e79d..1f0d589a2 100644 --- a/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt +++ b/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt @@ -20,6 +20,8 @@ import com.android.build.api.instrumentation.AsmClassVisitorFactory import com.android.build.api.instrumentation.FramesComputationMode import com.android.build.api.instrumentation.InstrumentationParameters import com.android.build.api.instrumentation.InstrumentationScope +import java.io.File +import org.gradle.api.Project /** * Compatibility version of [com.android.build.api.variant.Component] diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle index 869d88516..685ea7e37 100644 --- a/java/dagger/hilt/android/plugin/build.gradle +++ b/java/dagger/hilt/android/plugin/build.gradle @@ -1,8 +1,8 @@ buildscript { ext { - kotlin_version = "1.8.20" + kotlin_version = "1.9.20" agp_version = System.getenv('AGP_VERSION') ?: "7.2.0" - ksp_version = "$kotlin_version-1.0.11" + ksp_version = "$kotlin_version-1.0.14" pluginArtifactId = 'hilt-android-gradle-plugin' pluginId = 'com.google.dagger.hilt.android' } @@ -24,7 +24,12 @@ allprojects { mavenCentral() } } + +// Avoids conflict with BUILD file +project.buildDir = 'buildOut' + subprojects { + project.buildDir = 'buildOut' afterEvaluate { dependencies { // This is needed to align older versions of kotlin-stdlib. diff --git a/java/dagger/hilt/android/plugin/main/build.gradle b/java/dagger/hilt/android/plugin/main/build.gradle index 96a28b937..035ecea22 100644 --- a/java/dagger/hilt/android/plugin/main/build.gradle +++ b/java/dagger/hilt/android/plugin/main/build.gradle @@ -65,16 +65,16 @@ dependencies { compileOnly "com.android.tools.build:gradle:$agp_version" compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" compileOnly "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version" - implementation 'org.ow2.asm:asm:9.0' + implementation 'org.ow2.asm:asm:9.6' implementation "com.squareup:javapoet:1.13.0" testImplementation gradleTestKit() testImplementation 'junit:junit:4.12' testImplementation 'com.google.truth:truth:1.0.1' testImplementation 'org.javassist:javassist:3.26.0-GA' - testPluginCompile 'com.android.tools.build:gradle:7.1.2' - testPluginCompile 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0' - testPluginCompile 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.8.0-1.0.9' + testPluginCompile "com.android.tools.build:gradle:$agp_version" + testPluginCompile "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + testPluginCompile "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version" } // Configure the generating task of plugin-under-test-metadata.properties to @@ -85,14 +85,11 @@ tasks.withType(PluginUnderTestMetadata.class).named("pluginUnderTestMetadata").c it.pluginClasspath.from(configurations.testPluginCompile) } -kotlin { - jvmToolchain(11) -} - compileKotlin { kotlinOptions { allWarningsAsErrors = true freeCompilerArgs += [ "-opt-in=kotlin.ExperimentalStdlibApi" ] + jvmTarget = 11 } } diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt index da5fb3c3c..16e4af9c0 100644 --- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt +++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt @@ -36,15 +36,10 @@ import org.objectweb.asm.Opcodes class AndroidEntryPointClassVisitor( private val apiVersion: Int, nextClassVisitor: ClassVisitor, - private val additionalClasses: File + private val classContext: ClassContext ) : ClassVisitor(apiVersion, nextClassVisitor) { - interface AndroidEntryPointParams : InstrumentationParameters { - @get:Internal - val additionalClassesDir: Property<File> - } - - abstract class Factory : AsmClassVisitorFactory<AndroidEntryPointParams> { + abstract class Factory : AsmClassVisitorFactory<InstrumentationParameters.None> { override fun createClassVisitor( classContext: ClassContext, nextClassVisitor: ClassVisitor @@ -52,7 +47,7 @@ class AndroidEntryPointClassVisitor( return AndroidEntryPointClassVisitor( apiVersion = instrumentationContext.apiVersion.get(), nextClassVisitor = nextClassVisitor, - additionalClasses = parameters.get().additionalClassesDir.get() + classContext = classContext ) } @@ -198,34 +193,21 @@ class AndroidEntryPointClassVisitor( } /** - * Check if Hilt generated class is a BroadcastReceiver with the marker field which means + * Check if Hilt generated class is a BroadcastReceiver with the marker annotation which means * a super.onReceive invocation has to be inserted in the implementation. */ - private fun hasOnReceiveBytecodeInjectionMarker() = - findAdditionalClassFile(newSuperclassName).inputStream().use { - var hasMarker = false - ClassReader(it).accept( - object : ClassVisitor(apiVersion) { - override fun visitField( - access: Int, - name: String, - descriptor: String, - signature: String?, - value: Any? - ): FieldVisitor? { - if (name == "onReceiveBytecodeInjectionMarker") { - hasMarker = true - } - return null - } - }, - ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES - ) - return@use hasMarker - } + private fun hasOnReceiveBytecodeInjectionMarker(): Boolean { + val newSuperclassFQName = newSuperclassName.toFQName() + return classContext.loadClassData(newSuperclassFQName) + ?.classAnnotations?.contains(ON_RECEIVE_MARKER_ANNOTATION) + ?: error("Cannot load class $newSuperclassFQName!") + } - private fun findAdditionalClassFile(className: String) = - File(additionalClasses, "$className.class") + /** + * Return a fully qualified name from an internal name. + * See https://asm.ow2.io/javadoc/org/objectweb/asm/Type.html#getInternalName() + */ + private fun String.toFQName() = this.replace('/', '.') companion object { val ANDROID_ENTRY_POINT_ANNOTATIONS = setOf( @@ -235,5 +217,6 @@ class AndroidEntryPointClassVisitor( const val ON_RECEIVE_METHOD_NAME = "onReceive" const val ON_RECEIVE_METHOD_DESCRIPTOR = "(Landroid/content/Context;Landroid/content/Intent;)V" + const val ON_RECEIVE_MARKER_ANNOTATION = "dagger.hilt.android.internal.OnReceiveBytecodeInjectionMarker" } } diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt index 4e2b8788a..7b03e434a 100644 --- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt +++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt @@ -38,7 +38,6 @@ import dagger.hilt.android.plugin.util.getKaptConfigName import dagger.hilt.android.plugin.util.getKspConfigName import dagger.hilt.android.plugin.util.isKspTask import dagger.hilt.processor.internal.optionvalues.GradleProjectType -import java.io.File import javax.inject.Inject import org.gradle.api.JavaVersion import org.gradle.api.Plugin @@ -246,12 +245,9 @@ class HiltGradlePlugin @Inject constructor( fun registerTransform(androidComponent: ComponentCompat) { androidComponent.transformClassesWith( classVisitorFactoryImplClass = AndroidEntryPointClassVisitor.Factory::class.java, - scope = InstrumentationScope.PROJECT - ) { params -> - val classesDir = - File(project.buildDir, "intermediates/javac/${androidComponent.name}/classes") - params.additionalClassesDir.set(classesDir) - } + scope = InstrumentationScope.PROJECT, + instrumentationParamsConfig = {} + ) androidComponent.setAsmFramesComputationMode( FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS ) diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt index 7c8326b0f..f7c33dca4 100644 --- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt +++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/CopyTransform.kt @@ -16,7 +16,6 @@ package dagger.hilt.android.plugin.util -import org.gradle.api.artifacts.transform.CacheableTransform import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.TransformAction import org.gradle.api.artifacts.transform.TransformOutputs @@ -24,12 +23,13 @@ import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider import org.gradle.api.tasks.Classpath +import org.gradle.work.DisableCachingByDefault /** * A transform that registers the input file (usually a jar or a class) as an output and thus * changing from one artifact type to another. */ -@CacheableTransform +@DisableCachingByDefault(because = "Copying files does not benefit from caching") abstract class CopyTransform : TransformAction<TransformParameters.None> { @get:Classpath @get:InputArtifact diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt index 2c803b71a..a91ce35c6 100644 --- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt +++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt @@ -38,8 +38,20 @@ internal fun addKaptTaskProcessorOptions( component: ComponentCompat, produceArgProvider: (Task) -> CommandLineArgumentProvider ) = project.plugins.withId("kotlin-kapt") { + checkClass("org.jetbrains.kotlin.gradle.internal.KaptTask") { + """ + The KAPT plugin was detected to be applied but its task class could not be found. + + This is an indicator that the Hilt Gradle Plugin is using a different class loader because + it was declared at the root while KAPT was declared in a sub-project. To fix this, declare + both plugins in the same scope, i.e. either at the root (without applying them) or at the + sub-projects. + """.trimIndent() + } project.tasks.withType(KaptTask::class.java) { task -> - if (task.name == "kapt${component.name.capitalize()}Kotlin") { + if (task.name == "kapt${component.name.capitalize()}Kotlin" || + // Task names in shared/src/AndroidMain in KMP projects has a platform suffix. + task.name == "kapt${component.name.capitalize()}KotlinAndroid") { val argProvider = produceArgProvider.invoke(task) // TODO: Update once KT-58009 is fixed. try { @@ -60,16 +72,39 @@ internal fun addKspTaskProcessorOptions( component: ComponentCompat, produceArgProvider: (Task) -> CommandLineArgumentProvider ) = project.plugins.withId("com.google.devtools.ksp") { + checkClass("com.google.devtools.ksp.gradle.KspTaskJvm") { + """ + The KSP plugin was detected to be applied but its task class could not be found. + + This is an indicator that the Hilt Gradle Plugin is using a different class loader because + it was declared at the root while KSP was declared in a sub-project. To fix this, declare + both plugins in the same scope, i.e. either at the root (without applying them) or at the + sub-projects. + + See https://github.com/google/dagger/issues/3965 for more details. + """.trimIndent() + } project.tasks.withType(KspTaskJvm::class.java) { task -> - if (task.name == "ksp${component.name.capitalize()}Kotlin") { + if (task.name == "ksp${component.name.capitalize()}Kotlin" || + // Task names in shared/src/AndroidMain in KMP projects has a platform suffix. + task.name == "ksp${component.name.capitalize()}KotlinAndroid") { task.commandLineArgumentProviders.add(produceArgProvider.invoke(task)) } } } +private inline fun checkClass(fqn: String, msg: () -> String) { + try { + Class.forName(fqn) + } catch (ex: ClassNotFoundException) { + throw IllegalStateException(msg.invoke(), ex) + } +} + internal fun Task.isKspTask(): Boolean = try { val kspTaskClass = Class.forName("com.google.devtools.ksp.gradle.KspTask") kspTaskClass.isAssignableFrom(this::class.java) } catch (ex: ClassNotFoundException) { false -}
\ No newline at end of file +} + diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryA/build.gradle b/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryA/build.gradle index e0ec23097..9e8a097b2 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryA/build.gradle +++ b/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryA/build.gradle @@ -4,12 +4,12 @@ plugins { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + buildToolsVersion "33.0.0" defaultConfig { minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 versionCode 1 versionName "1.0" } diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryC/build.gradle b/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryC/build.gradle index 68c6a97f3..450f128aa 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryC/build.gradle +++ b/java/dagger/hilt/android/plugin/main/src/test/data/android-libraryC/build.gradle @@ -4,12 +4,12 @@ plugins { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + buildToolsVersion "33.0.0" defaultConfig { minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 versionCode 1 versionName "1.0" } diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/app/build.gradle b/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/app/build.gradle index 23cd028b9..954e209d9 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/app/build.gradle +++ b/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/app/build.gradle @@ -20,8 +20,8 @@ plugins { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + buildToolsVersion "33.0.0" flavorDimensions 'api', 'version' productFlavors { @@ -46,7 +46,7 @@ android { defaultConfig { applicationId "simple.app" minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 } compileOptions { diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/feature/build.gradle b/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/feature/build.gradle index 8e3495156..068d402fa 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/feature/build.gradle +++ b/java/dagger/hilt/android/plugin/main/src/test/data/flavored-project/feature/build.gradle @@ -20,8 +20,8 @@ plugins { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + buildToolsVersion "33.0.0" flavorDimensions 'api', 'version' productFlavors { @@ -45,7 +45,7 @@ android { defaultConfig { minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 33 } compileOptions { diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/build.gradle b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/build.gradle index 2173a8280..506d40a81 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/build.gradle +++ b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/build.gradle @@ -20,13 +20,13 @@ plugins { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + namespace "simple.app" defaultConfig { applicationId "simple.app" minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 } compileOptions { diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/src/main/AndroidManifest.xml b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/src/main/AndroidManifest.xml index da45ecc46..ce521fba8 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/src/main/AndroidManifest.xml +++ b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/src/main/AndroidManifest.xml @@ -16,5 +16,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="simple.app"> <application android:name=".SimpleApp" android:label="Flavored App"> + <receiver android:name=".SimpleReceiver" android:exported="false"> + </receiver> </application> </manifest>
\ No newline at end of file diff --git a/java/dagger/spi/model/CompilerEnvironment.java b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/src/main/java/simple/app/SimpleReceiver.java index 28553e4c3..ce483add8 100644 --- a/java/dagger/spi/model/CompilerEnvironment.java +++ b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/app/src/main/java/simple/app/SimpleReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Dagger Authors. + * Copyright (C) 2023 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. @@ -14,10 +14,15 @@ * limitations under the License. */ -package dagger.spi.model; +package simple.app; -/** Types for the compiler in use for annotation processing. */ -public enum CompilerEnvironment { - JAVA, - KSP +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint +class SimpleReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) {} } diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/feature/build.gradle b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/feature/build.gradle index e19f1d571..57f7a742b 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/feature/build.gradle +++ b/java/dagger/hilt/android/plugin/main/src/test/data/simple-project-for-agp-test/feature/build.gradle @@ -20,12 +20,12 @@ plugins { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + namespace "simple.library" defaultConfig { minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 33 } compileOptions { diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/AGPCompatibilityTest.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/AGPCompatibilityTest.kt index d5bc48802..f488fb332 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/AGPCompatibilityTest.kt +++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/AGPCompatibilityTest.kt @@ -26,11 +26,13 @@ import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith import org.junit.runners.Parameterized -const val TASK = ":app:hiltJavaCompileDebug" +// `hiltJavaCompileDebug` gets to run as well as `transformDebugClassesWithAsm` depends on it. +const val TASK = ":app:transformDebugClassesWithAsm" @RunWith(Parameterized::class) class AGPCompatibilityTest( - private val agpVersion: String + private val agpVersion: String, + private val gradleVersion: String ) { @get:Rule val testProjectDir = TemporaryFolder() @@ -75,18 +77,21 @@ class AGPCompatibilityTest( GradleRunner.create() .withProjectDir(testProjectDir.root) .withArguments(*args) + .withGradleVersion(gradleVersion) .forwardOutput() return gradleRunner.build() } companion object { @JvmStatic - @Parameterized.Parameters(name = "agpVersion = {0}") + @Parameterized.Parameters(name = "agpVersion = {0}, gradleVersion = {1}") fun parameters() = listOf( - arrayOf("7.2.0"), - arrayOf("7.1.0"), - arrayOf("7.0.0"), + // AGP 8.3 requires Gradle 8.4 and JDK 17. + arrayOf("8.3.0-alpha11", "8.4"), + arrayOf("7.2.0", "7.4.2"), + arrayOf("7.1.0", "7.4.2"), + arrayOf("7.0.0", "7.4.2"), ) } } diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/BuildCacheTest.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/BuildCacheTest.kt index 4c34dd621..c5a67afbb 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/BuildCacheTest.kt +++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/BuildCacheTest.kt @@ -82,8 +82,6 @@ class BuildCacheTest(private val enableAggregatingTask: Boolean) { val secondResult = secondGradleRunner.build() val cacheableTasks: List<String> = mutableListOf<String>().apply { - add(":checkDebugAarMetadata") - add(":checkDebugDuplicateClasses") add(":compileDebugJavaWithJavac") add(":compressDebugAssets") add(":desugarDebugFileDependencies") @@ -105,9 +103,6 @@ class BuildCacheTest(private val enableAggregatingTask: Boolean) { add(":mergeProjectDexDebug") add(":processDebugManifestForPackage") add(":transformDebugClassesWithAsm") - add(":validateSigningDebug") - add(":writeDebugAppMetadata") - add(":writeDebugSigningConfigVersions") } val tasksFromCache = diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt index fb47bdb13..221dfcfd4 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt +++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt @@ -156,13 +156,13 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + buildToolsVersion "33.0.0" defaultConfig { ${ if (isAppProject) "applicationId \"plugin.test\"" else "" } minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 } compileOptions { @@ -189,7 +189,8 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { ${hiltOptions.joinToString(separator = "\n")} } ${additionalClosures.joinToString(separator = "\n")} - """.trimIndent() + """ + .trimIndent() ) } } @@ -198,9 +199,14 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { gradlePropertiesFile?.delete() gradlePropertiesFile = tempFolder.newFile("gradle.properties").apply { - writeText(""" + writeText( + """ android.useAndroidX=true - """.trimIndent()) + // TODO(b/296583777): See if there's a better way to fix the OOM error. + org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g + """ + .trimIndent() + ) } } @@ -218,7 +224,8 @@ class GradleTestRunner(val tempFolder: TemporaryFolder) { ${activities.joinToString(separator = "\n")} </application> </manifest> - """.trimIndent() + """ + .trimIndent() ) } } diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt index 1e8490f1b..4fb25a0b3 100644 --- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt +++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt @@ -34,11 +34,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) class IncrementalProcessorTest(private val incapMode: String) { - @get:Rule - val testProjectDir = TemporaryFolder() + @get:Rule val testProjectDir = TemporaryFolder() - @get:Rule - val expect: Expect = Expect.create() + @get:Rule val expect: Expect = Expect.create() // Original source files private lateinit var srcApp: File @@ -108,16 +106,18 @@ class IncrementalProcessorTest(private val incapMode: String) { private lateinit var unchangedFiles: Set<File> private lateinit var deletedFiles: Set<File> - private val compileTaskName = if (incapMode == ISOLATING_MODE) { - ":hiltJavaCompileDebug" - } else { - ":compileDebugJavaWithJavac" - } - private val testCompileTaskName = if (incapMode == ISOLATING_MODE) { - ":hiltJavaCompileDebugUnitTest" - } else { - ":compileDebugUnitTestJavaWithJavac" - } + private val compileTaskName = + if (incapMode == ISOLATING_MODE) { + ":hiltJavaCompileDebug" + } else { + ":compileDebugJavaWithJavac" + } + private val testCompileTaskName = + if (incapMode == ISOLATING_MODE) { + ":hiltJavaCompileDebugUnitTest" + } else { + ":compileDebugUnitTestJavaWithJavac" + } private val aggregatingTaskName = ":hiltAggregateDepsDebug" private val testAggregatingTaskName = ":hiltAggregateDepsDebugUnitTest" @@ -128,8 +128,9 @@ class IncrementalProcessorTest(private val incapMode: String) { File("src/test/data/simple-project").copyRecursively(projectRoot) // set up build file - File(projectRoot, "build.gradle").writeText( - """ + File(projectRoot, "build.gradle") + .writeText( + """ buildscript { repositories { google() @@ -146,13 +147,13 @@ class IncrementalProcessorTest(private val incapMode: String) { } android { - compileSdkVersion 32 - buildToolsVersion "32.0.0" + compileSdkVersion 33 + buildToolsVersion "33.0.0" defaultConfig { applicationId "hilt.simple" minSdkVersion 21 - targetSdkVersion 32 + targetSdkVersion 33 javaCompileOptions { annotationProcessorOptions { arguments += ["dagger.hilt.shareTestComponents" : "true"] @@ -190,32 +191,36 @@ class IncrementalProcessorTest(private val incapMode: String) { hilt { enableAggregatingTask = ${if (incapMode == ISOLATING_MODE) "true" else "false"} } - """.trimIndent() - ) + """ + .trimIndent() + ) // Compute directory paths val defaultGenSrcDir = "build/generated/ap_generated_sources/debug/out/" - fun getComponentTreeDepsGenSrcDir(variant: String) = if (incapMode == ISOLATING_MODE) { - "build/generated/hilt/component_trees/$variant/" - } else { - "build/generated/ap_generated_sources/$variant/out/" - } + fun getComponentTreeDepsGenSrcDir(variant: String) = + if (incapMode == ISOLATING_MODE) { + "build/generated/hilt/component_trees/$variant/" + } else { + "build/generated/ap_generated_sources/$variant/out/" + } val componentTreeDepsGenSrcDir = getComponentTreeDepsGenSrcDir("debug") val testComponentTreeDepsGenSrcDir = getComponentTreeDepsGenSrcDir("debugUnitTest") - fun getRootGenSrcDir(variant: String) = if (incapMode == ISOLATING_MODE) { - "build/generated/hilt/component_sources/$variant/" - } else { - "build/generated/ap_generated_sources/$variant/out/" - } + fun getRootGenSrcDir(variant: String) = + if (incapMode == ISOLATING_MODE) { + "build/generated/hilt/component_sources/$variant/" + } else { + "build/generated/ap_generated_sources/$variant/out/" + } val rootGenSrcDir = getRootGenSrcDir("debug") val testRootGenSrcDir = getRootGenSrcDir("debugUnitTest") val defaultClassesDir = "build/intermediates/javac/debug/classes" val testDefaultClassesDir = "build/intermediates/javac/debugUnitTest/classes" - fun getRootClassesDir(variant: String) = if (incapMode == ISOLATING_MODE) { - "build/intermediates/hilt/component_classes/$variant/" - } else { - "build/intermediates/javac/$variant/classes" - } + fun getRootClassesDir(variant: String) = + if (incapMode == ISOLATING_MODE) { + "build/intermediates/hilt/component_classes/$variant/" + } else { + "build/intermediates/javac/$variant/classes" + } val rootClassesDir = getRootClassesDir("debug") val testRootClassesDir = getRootClassesDir("debugUnitTest") @@ -236,59 +241,64 @@ class IncrementalProcessorTest(private val incapMode: String) { File(projectRoot, "$defaultGenSrcDir/simple/Activity1_GeneratedInjector.java") genActivityInjector2 = File(projectRoot, "$defaultGenSrcDir/simple/Activity2_GeneratedInjector.java") - genAppInjectorDeps = File( - projectRoot, - "$defaultGenSrcDir/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.java" - ) - genActivityInjectorDeps1 = File( - projectRoot, - "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.java" - ) - genActivityInjectorDeps2 = File( - projectRoot, - "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.java" - ) - genModuleDeps1 = File( - projectRoot, - "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Module1.java" - ) + genAppInjectorDeps = + File( + projectRoot, + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.java" + ) + genActivityInjectorDeps1 = + File( + projectRoot, + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.java" + ) + genActivityInjectorDeps2 = + File( + projectRoot, + "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.java" + ) + genModuleDeps1 = + File(projectRoot, "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Module1.java") genModuleDeps2 = File(projectRoot, "$defaultGenSrcDir/hilt_aggregated_deps/_simple_Module2.java") genComponentTreeDeps = File(projectRoot, "$componentTreeDepsGenSrcDir/simple/SimpleApp_ComponentTreeDeps.java") genHiltComponents = File(projectRoot, "$rootGenSrcDir/simple/SimpleApp_HiltComponents.java") - genDaggerHiltApplicationComponent = File( - projectRoot, - "$rootGenSrcDir/simple/DaggerSimpleApp_HiltComponents_SingletonC.java" - ) - genTest1ComponentTreeDeps = File( - projectRoot, - testComponentTreeDepsGenSrcDir + - "/dagger/hilt/android/internal/testing/root/Test1_ComponentTreeDeps.java" - ) - genTest2ComponentTreeDeps = File( - projectRoot, - testComponentTreeDepsGenSrcDir + - "/dagger/hilt/android/internal/testing/root/Test2_ComponentTreeDeps.java" - ) - genTest1HiltComponents = File( - projectRoot, - "$testRootGenSrcDir/dagger/hilt/android/internal/testing/root/Test1_HiltComponents.java" - ) - genTest2HiltComponents = File( - projectRoot, - "$testRootGenSrcDir/dagger/hilt/android/internal/testing/root/Test2_HiltComponents.java" - ) - genTest1DaggerHiltApplicationComponent = File( - projectRoot, - testRootGenSrcDir + - "/dagger/hilt/android/internal/testing/root/DaggerTest1_HiltComponents_SingletonC.java" - ) - genTest2DaggerHiltApplicationComponent = File( - projectRoot, - testRootGenSrcDir + - "/dagger/hilt/android/internal/testing/root/DaggerTest2_HiltComponents_SingletonC.java" - ) + genDaggerHiltApplicationComponent = + File(projectRoot, "$rootGenSrcDir/simple/DaggerSimpleApp_HiltComponents_SingletonC.java") + genTest1ComponentTreeDeps = + File( + projectRoot, + testComponentTreeDepsGenSrcDir + + "/dagger/hilt/android/internal/testing/root/Test1_ComponentTreeDeps.java" + ) + genTest2ComponentTreeDeps = + File( + projectRoot, + testComponentTreeDepsGenSrcDir + + "/dagger/hilt/android/internal/testing/root/Test2_ComponentTreeDeps.java" + ) + genTest1HiltComponents = + File( + projectRoot, + "$testRootGenSrcDir/dagger/hilt/android/internal/testing/root/Test1_HiltComponents.java" + ) + genTest2HiltComponents = + File( + projectRoot, + "$testRootGenSrcDir/dagger/hilt/android/internal/testing/root/Test2_HiltComponents.java" + ) + genTest1DaggerHiltApplicationComponent = + File( + projectRoot, + testRootGenSrcDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest1_HiltComponents_SingletonC.java" + ) + genTest2DaggerHiltApplicationComponent = + File( + projectRoot, + testRootGenSrcDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest2_HiltComponents_SingletonC.java" + ) classSrcApp = File(projectRoot, "$defaultClassesDir/simple/SimpleApp.class") classSrcActivity1 = File(projectRoot, "$defaultClassesDir/simple/Activity1.class") @@ -302,70 +312,69 @@ class IncrementalProcessorTest(private val incapMode: String) { classGenHiltActivity2 = File(projectRoot, "$defaultClassesDir/simple/Hilt_Activity2.class") classGenAppInjector = File(projectRoot, "$defaultClassesDir/simple/SimpleApp_GeneratedInjector.class") - classGenActivityInjector1 = File( - projectRoot, - "$defaultClassesDir/simple/Activity1_GeneratedInjector.class" - ) - classGenActivityInjector2 = File( - projectRoot, - "$defaultClassesDir/simple/Activity2_GeneratedInjector.class" - ) - classGenAppInjectorDeps = File( - projectRoot, - "$defaultClassesDir/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.class" - ) - classGenActivityInjectorDeps1 = File( - projectRoot, - "$defaultClassesDir/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.class" - ) - classGenActivityInjectorDeps2 = File( - projectRoot, - "$defaultClassesDir/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.class" - ) + classGenActivityInjector1 = + File(projectRoot, "$defaultClassesDir/simple/Activity1_GeneratedInjector.class") + classGenActivityInjector2 = + File(projectRoot, "$defaultClassesDir/simple/Activity2_GeneratedInjector.class") + classGenAppInjectorDeps = + File( + projectRoot, + "$defaultClassesDir/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.class" + ) + classGenActivityInjectorDeps1 = + File( + projectRoot, + "$defaultClassesDir/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.class" + ) + classGenActivityInjectorDeps2 = + File( + projectRoot, + "$defaultClassesDir/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.class" + ) classGenModuleDeps1 = File(projectRoot, "$defaultClassesDir/hilt_aggregated_deps/_simple_Module1.class") classGenModuleDeps2 = File(projectRoot, "$defaultClassesDir/hilt_aggregated_deps/_simple_Module2.class") - classGenComponentTreeDeps = File( - projectRoot, - "$rootClassesDir/simple/SimpleApp_ComponentTreeDeps.class" - ) - classGenHiltComponents = File( - projectRoot, - "$rootClassesDir/simple/SimpleApp_HiltComponents.class" - ) - classGenDaggerHiltApplicationComponent = File( - projectRoot, - "$rootClassesDir/simple/DaggerSimpleApp_HiltComponents_SingletonC.class" - ) - classGenTest1ComponentTreeDeps = File( - projectRoot, - testRootClassesDir + - "/dagger/hilt/android/internal/testing/root/Test1_ComponentTreeDeps.class" - ) - classGenTest2ComponentTreeDeps = File( - projectRoot, - testRootClassesDir + - "/dagger/hilt/android/internal/testing/root/Test2_ComponentTreeDeps.class" - ) - classGenTest1HiltComponents = File( - projectRoot, - "$testRootClassesDir/dagger/hilt/android/internal/testing/root/Test1_HiltComponents.class" - ) - classGenTest2HiltComponents = File( - projectRoot, - "$testRootClassesDir/dagger/hilt/android/internal/testing/root/Test2_HiltComponents.class" - ) - classGenTest1DaggerHiltApplicationComponent = File( - projectRoot, - testRootClassesDir + - "/dagger/hilt/android/internal/testing/root/DaggerTest1_HiltComponents_SingletonC.class" - ) - classGenTest2DaggerHiltApplicationComponent = File( - projectRoot, - testRootClassesDir + - "/dagger/hilt/android/internal/testing/root/DaggerTest2_HiltComponents_SingletonC.class" - ) + classGenComponentTreeDeps = + File(projectRoot, "$rootClassesDir/simple/SimpleApp_ComponentTreeDeps.class") + classGenHiltComponents = + File(projectRoot, "$rootClassesDir/simple/SimpleApp_HiltComponents.class") + classGenDaggerHiltApplicationComponent = + File(projectRoot, "$rootClassesDir/simple/DaggerSimpleApp_HiltComponents_SingletonC.class") + classGenTest1ComponentTreeDeps = + File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/Test1_ComponentTreeDeps.class" + ) + classGenTest2ComponentTreeDeps = + File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/Test2_ComponentTreeDeps.class" + ) + classGenTest1HiltComponents = + File( + projectRoot, + "$testRootClassesDir/dagger/hilt/android/internal/testing/root/Test1_HiltComponents.class" + ) + classGenTest2HiltComponents = + File( + projectRoot, + "$testRootClassesDir/dagger/hilt/android/internal/testing/root/Test2_HiltComponents.class" + ) + classGenTest1DaggerHiltApplicationComponent = + File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest1_HiltComponents_SingletonC.class" + ) + classGenTest2DaggerHiltApplicationComponent = + File( + projectRoot, + testRootClassesDir + + "/dagger/hilt/android/internal/testing/root/DaggerTest2_HiltComponents_SingletonC.class" + ) } @Test @@ -428,13 +437,15 @@ class IncrementalProcessorTest(private val incapMode: String) { // Change Activity 1 source searchAndReplace( - srcActivity1, "// Insert-change", + srcActivity1, + "// Insert-change", """ @Override public void onResume() { super.onResume(); } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalBuild() @@ -442,66 +453,69 @@ class IncrementalProcessorTest(private val incapMode: String) { // Check annotation processing outputs // * Only activity 1 sources are re-generated, isolation in modules and from other activities - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task did not run, no change in deps - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) - // * Components are re-generated due to a recompilation of a dep - listOf( - genHiltApp, // Re-gen because components got re-gen - genHiltActivity1, - genActivityInjector1, - genActivityInjectorDeps1, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } else { - // * Root classes along with components are always re-generated (aggregated processor) - listOf( - genHiltApp, - genHiltActivity1, - genAppInjector, - genActivityInjector1, - genAppInjectorDeps, - genActivityInjectorDeps1, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Components are re-generated due to a recompilation of a dep + listOf( + genHiltApp, // Re-gen because components got re-gen + genHiltActivity1, + genActivityInjector1, + genActivityInjectorDeps1, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genHiltActivity1, + genAppInjector, + genActivityInjector1, + genAppInjectorDeps, + genActivityInjectorDeps1, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) - expect.withMessage("Full build") - .that(componentTreeDepsFullBuild) - .isEqualTo(componentTreeDepsIncrementalBuild) + expect + .withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs // * Gen sources from activity 1 are re-compiled - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classSrcActivity1, - classGenHiltApp, - classGenHiltActivity1, - classGenActivityInjector1, - classGenActivityInjectorDeps1, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent, - ) - } else { - // * All aggregating processor gen sources are re-compiled - listOf( - classSrcActivity1, - classGenHiltApp, - classGenHiltActivity1, - classGenAppInjector, - classGenActivityInjector1, - classGenAppInjectorDeps, - classGenActivityInjectorDeps1, - classGenHiltComponents, - classGenComponentTreeDeps, - classGenDaggerHiltApplicationComponent - ) - } + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classSrcActivity1, + classGenHiltApp, + classGenHiltActivity1, + classGenActivityInjector1, + classGenActivityInjectorDeps1, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent, + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcActivity1, + classGenHiltApp, + classGenHiltActivity1, + classGenAppInjector, + classGenActivityInjector1, + classGenAppInjectorDeps, + classGenActivityInjectorDeps1, + classGenHiltComponents, + classGenComponentTreeDeps, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -512,76 +526,82 @@ class IncrementalProcessorTest(private val incapMode: String) { // Change Activity 1 source searchAndReplace( - srcActivity1, "// Insert-change", + srcActivity1, + "// Insert-change", """ private void foo() { } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalBuild() - val expectedOutcome = if (incapMode == ISOLATING_MODE) { - // In isolating mode, changes that do not affect ABI will not cause re-compilation. - TaskOutcome.UP_TO_DATE - } else { - TaskOutcome.SUCCESS - } + val expectedOutcome = + if (incapMode == ISOLATING_MODE) { + // In isolating mode, changes that do not affect ABI will not cause re-compilation. + TaskOutcome.UP_TO_DATE + } else { + TaskOutcome.SUCCESS + } expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(expectedOutcome) // Check annotation processing outputs // * Only activity 1 sources are re-generated, isolation in modules and from other activities - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task did not run, no change in deps - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) - listOf( - genHiltActivity1, - genActivityInjector1, - genActivityInjectorDeps1, - ) - } else { - // * Root classes along with components are always re-generated (aggregated processor) - listOf( - genHiltApp, - genHiltActivity1, - genAppInjector, - genActivityInjector1, - genAppInjectorDeps, - genActivityInjectorDeps1, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + listOf( + genHiltActivity1, + genActivityInjector1, + genActivityInjectorDeps1, + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genHiltActivity1, + genAppInjector, + genActivityInjector1, + genAppInjectorDeps, + genActivityInjectorDeps1, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) - expect.withMessage("Full build") - .that(componentTreeDepsFullBuild) - .isEqualTo(componentTreeDepsIncrementalBuild) + expect + .withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs // * Gen sources from activity 1 are re-compiled - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classSrcActivity1, - classGenHiltActivity1, - classGenActivityInjector1, - classGenActivityInjectorDeps1 - ) - } else { - // * All aggregating processor gen sources are re-compiled - listOf( - classSrcActivity1, - classGenHiltApp, - classGenHiltActivity1, - classGenAppInjector, - classGenActivityInjector1, - classGenAppInjectorDeps, - classGenActivityInjectorDeps1, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classSrcActivity1, + classGenHiltActivity1, + classGenActivityInjector1, + classGenActivityInjectorDeps1 + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcActivity1, + classGenHiltApp, + classGenHiltActivity1, + classGenAppInjector, + classGenActivityInjector1, + classGenAppInjectorDeps, + classGenActivityInjectorDeps1, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -592,13 +612,15 @@ class IncrementalProcessorTest(private val incapMode: String) { // Change Module 1 source searchAndReplace( - srcModule1, "// Insert-change", + srcModule1, + "// Insert-change", """ @Provides static double provideDouble() { return 10.10; } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalBuild() @@ -606,58 +628,61 @@ class IncrementalProcessorTest(private val incapMode: String) { // Check annotation processing outputs // * Only module 1 sources are re-generated, isolation from other modules - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task did not run, no change in deps - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) - // * Components are re-generated due to a recompilation of a dep - listOf( - genHiltApp, // Re-generated because components got re-generated - genModuleDeps1, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } else { - // * Root classes along with components are always re-generated (aggregated processor) - listOf( - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genModuleDeps1, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Components are re-generated due to a recompilation of a dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genModuleDeps1, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genModuleDeps1, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) - expect.withMessage("Full build") - .that(componentTreeDepsFullBuild) - .isEqualTo(componentTreeDepsIncrementalBuild) + expect + .withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs // * Gen sources from module 1 are re-compiled - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classSrcModule1, - classGenHiltApp, - classGenModuleDeps1, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } else { - // * All aggregating processor gen sources are re-compiled - listOf( - classSrcModule1, - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenModuleDeps1, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classSrcModule1, + classGenHiltApp, + classGenModuleDeps1, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcModule1, + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenModuleDeps1, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -668,13 +693,15 @@ class IncrementalProcessorTest(private val incapMode: String) { // Change Application source searchAndReplace( - srcApp, "// Insert-change", + srcApp, + "// Insert-change", """ @Override public void onCreate() { super.onCreate(); } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalBuild() @@ -682,57 +709,60 @@ class IncrementalProcessorTest(private val incapMode: String) { // Check annotation processing outputs // * No modules or activities (or any other non-root) classes should be generated - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task did not run, no change in deps - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) - // * Components are re-generated due to a recompilation of a dep - listOf( - genHiltApp, // Re-generated because components got re-generated - genAppInjector, - genAppInjectorDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } else { - // * Root classes along with components are always re-generated (aggregated processor) - listOf( - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Components are re-generated due to a recompilation of a dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genAppInjector, + genAppInjectorDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) val componentTreeDepsIncrementalBuild = genComponentTreeDeps.readText(Charsets.UTF_8) - expect.withMessage("Full build") - .that(componentTreeDepsFullBuild) - .isEqualTo(componentTreeDepsIncrementalBuild) + expect + .withMessage("Full build") + .that(componentTreeDepsFullBuild) + .isEqualTo(componentTreeDepsIncrementalBuild) // Check compilation outputs - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classSrcApp, - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } else { - // * All aggregating processor gen sources are re-compiled - listOf( - classSrcApp, - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classSrcApp, + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + // * All aggregating processor gen sources are re-compiled + listOf( + classSrcApp, + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -749,33 +779,28 @@ class IncrementalProcessorTest(private val incapMode: String) { // * All related gen classes from activity 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged // * Root classes along with components are always re-generated (aggregated processor) - assertDeletedFiles( - listOf( - genHiltActivity2, - genActivityInjector2, - genActivityInjectorDeps2 - ) - ) - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task ran due to a change in dep - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) - // * Components are re-generated since there was a change in dep - listOf( - genHiltApp, // Re-generated because components got re-generated - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } else { - listOf( - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + assertDeletedFiles(listOf(genHiltActivity2, genActivityInjector2, genActivityInjectorDeps2)) + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task ran due to a change in dep + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + // * Components are re-generated since there was a change in dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) // Check compilation outputs @@ -789,23 +814,24 @@ class IncrementalProcessorTest(private val incapMode: String) { classGenActivityInjectorDeps2 ) ) - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classGenHiltApp, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } else { - listOf( - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classGenHiltApp, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + listOf( + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -822,58 +848,53 @@ class IncrementalProcessorTest(private val incapMode: String) { // * All related gen classes from module 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged - assertDeletedFiles( - listOf(genModuleDeps2) - ) - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task ran due to a change in dep - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) - // * Components are re-generated since there was a change in dep - listOf( - genHiltApp, // Re-generated because components got re-generated - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } else { - // * Root classes along with components are always re-generated (aggregated processor) - listOf( - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + assertDeletedFiles(listOf(genModuleDeps2)) + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task ran due to a change in dep + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.SUCCESS) + // * Components are re-generated since there was a change in dep + listOf( + genHiltApp, // Re-generated because components got re-generated + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } else { + // * Root classes along with components are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) // Check compilation outputs // * All compiled classes from module 2 should be deleted // * Unrelated activities and modules are in isolation and should be unchanged - assertDeletedFiles( - listOf( - classSrcModule2, - classGenModuleDeps2 - ) - ) - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classGenHiltApp, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } else { - listOf( - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } + assertDeletedFiles(listOf(classSrcModule2, classGenModuleDeps2)) + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classGenHiltApp, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } else { + listOf( + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -887,48 +908,52 @@ class IncrementalProcessorTest(private val incapMode: String) { package simple; public class Foo { } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalBuild() - val expectedOutcome = if (incapMode == ISOLATING_MODE) { - // In isolating mode, component compile task does not re-compile. - TaskOutcome.UP_TO_DATE - } else { - TaskOutcome.SUCCESS - } + val expectedOutcome = + if (incapMode == ISOLATING_MODE) { + // In isolating mode, component compile task does not re-compile. + TaskOutcome.UP_TO_DATE + } else { + TaskOutcome.SUCCESS + } expect.that(result.task(compileTaskName)!!.outcome).isEqualTo(expectedOutcome) - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - // * Aggregating task did not run, no change in deps - expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) - // * Non-DI related source causes no files to be generated - emptyList() - } else { - // * Root classes are always re-generated (aggregated processor) - listOf( - genHiltApp, - genAppInjector, - genAppInjectorDeps, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + // * Aggregating task did not run, no change in deps + expect.that(result.task(aggregatingTaskName)!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) + // * Non-DI related source causes no files to be generated + emptyList() + } else { + // * Root classes are always re-generated (aggregated processor) + listOf( + genHiltApp, + genAppInjector, + genAppInjectorDeps, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - emptyList() - } else { - listOf( - classGenHiltApp, - classGenAppInjector, - classGenAppInjectorDeps, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent - ) - } + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + emptyList() + } else { + listOf( + classGenHiltApp, + classGenAppInjector, + classGenAppInjectorDeps, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -970,11 +995,13 @@ class IncrementalProcessorTest(private val incapMode: String) { // Change Test 1 source searchAndReplace( - srcTest1, "// Insert-change", + srcTest1, + "// Insert-change", """ @Test public void newTest() { } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalTestBuild() @@ -983,49 +1010,53 @@ class IncrementalProcessorTest(private val incapMode: String) { // Check annotation processing outputs // * Unrelated test components should be unchanged - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - listOf( - genTest1HiltComponents, - genTest1DaggerHiltApplicationComponent, - ) - } else { - listOf( - genTest1ComponentTreeDeps, - genTest2ComponentTreeDeps, - genTest1HiltComponents, - genTest2HiltComponents, - genTest1DaggerHiltApplicationComponent, - genTest2DaggerHiltApplicationComponent, - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + genTest1HiltComponents, + genTest1DaggerHiltApplicationComponent, + ) + } else { + listOf( + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) val test1ComponentTreeDepsIncrementalBuild = genTest1ComponentTreeDeps.readText(Charsets.UTF_8) val test2ComponentTreeDepsIncrementalBuild = genTest2ComponentTreeDeps.readText(Charsets.UTF_8) - expect.withMessage("Full build") - .that(test1ComponentTreeDepsFullBuild) - .isEqualTo(test1ComponentTreeDepsIncrementalBuild) - expect.withMessage("Full build") - .that(test2ComponentTreeDepsFullBuild) - .isEqualTo(test2ComponentTreeDepsIncrementalBuild) - - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf( - classSrcTest1, - classGenTest1HiltComponents, - classGenTest1DaggerHiltApplicationComponent, - ) - } else { - listOf( - classSrcTest1, - classGenTest1ComponentTreeDeps, - classGenTest2ComponentTreeDeps, - classGenTest1HiltComponents, - classGenTest2HiltComponents, - classGenTest1DaggerHiltApplicationComponent, - classGenTest2DaggerHiltApplicationComponent, - ) - } + expect + .withMessage("Full build") + .that(test1ComponentTreeDepsFullBuild) + .isEqualTo(test1ComponentTreeDepsIncrementalBuild) + expect + .withMessage("Full build") + .that(test2ComponentTreeDepsFullBuild) + .isEqualTo(test2ComponentTreeDepsIncrementalBuild) + + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf( + classSrcTest1, + classGenTest1HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + ) + } else { + listOf( + classSrcTest1, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -1037,60 +1068,67 @@ class IncrementalProcessorTest(private val incapMode: String) { // Change Test 1 source searchAndReplace( - srcTest1, "// Insert-change", + srcTest1, + "// Insert-change", """ private void newMethod() { } - """.trimIndent() + """ + .trimIndent() ) val result = runIncrementalTestBuild() - val expectedOutcome = if (incapMode == ISOLATING_MODE) { - // In isolating mode, changes that do not affect ABI will not cause re-compilation. - TaskOutcome.UP_TO_DATE - } else { - TaskOutcome.SUCCESS - } + val expectedOutcome = + if (incapMode == ISOLATING_MODE) { + // In isolating mode, changes that do not affect ABI will not cause re-compilation. + TaskOutcome.UP_TO_DATE + } else { + TaskOutcome.SUCCESS + } expect.that(result.task(testCompileTaskName)!!.outcome).isEqualTo(expectedOutcome) // Check annotation processing outputs // * Unrelated test components should be unchanged - val regeneratedSourceFiles = if (incapMode == ISOLATING_MODE) { - emptyList() - } else { - listOf( - genTest1ComponentTreeDeps, - genTest2ComponentTreeDeps, - genTest1HiltComponents, - genTest2HiltComponents, - genTest1DaggerHiltApplicationComponent, - genTest2DaggerHiltApplicationComponent, - ) - } + val regeneratedSourceFiles = + if (incapMode == ISOLATING_MODE) { + emptyList() + } else { + listOf( + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, + ) + } assertChangedFiles(FileType.JAVA, regeneratedSourceFiles) val test1ComponentTreeDepsIncrementalBuild = genTest1ComponentTreeDeps.readText(Charsets.UTF_8) val test2ComponentTreeDepsIncrementalBuild = genTest2ComponentTreeDeps.readText(Charsets.UTF_8) - expect.withMessage("Full build") - .that(test1ComponentTreeDepsFullBuild) - .isEqualTo(test1ComponentTreeDepsIncrementalBuild) - expect.withMessage("Full build") - .that(test2ComponentTreeDepsFullBuild) - .isEqualTo(test2ComponentTreeDepsIncrementalBuild) - - val recompiledClassFiles = if (incapMode == ISOLATING_MODE) { - listOf(classSrcTest1) - } else { - listOf( - classSrcTest1, - classGenTest1ComponentTreeDeps, - classGenTest2ComponentTreeDeps, - classGenTest1HiltComponents, - classGenTest2HiltComponents, - classGenTest1DaggerHiltApplicationComponent, - classGenTest2DaggerHiltApplicationComponent, - ) - } + expect + .withMessage("Full build") + .that(test1ComponentTreeDepsFullBuild) + .isEqualTo(test1ComponentTreeDepsIncrementalBuild) + expect + .withMessage("Full build") + .that(test2ComponentTreeDepsFullBuild) + .isEqualTo(test2ComponentTreeDepsIncrementalBuild) + + val recompiledClassFiles = + if (incapMode == ISOLATING_MODE) { + listOf(classSrcTest1) + } else { + listOf( + classSrcTest1, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, + ) + } assertChangedFiles(FileType.CLASS, recompiledClassFiles) } @@ -1129,83 +1167,93 @@ class IncrementalProcessorTest(private val incapMode: String) { } private fun recordTimestamps() { - val files = listOf( - genHiltApp, - genHiltActivity1, - genHiltActivity2, - genAppInjector, - genActivityInjector1, - genActivityInjector2, - genAppInjectorDeps, - genActivityInjectorDeps1, - genActivityInjectorDeps2, - genModuleDeps1, - genModuleDeps2, - genComponentTreeDeps, - genHiltComponents, - genDaggerHiltApplicationComponent, - genTest1ComponentTreeDeps, - genTest2ComponentTreeDeps, - genTest1HiltComponents, - genTest2HiltComponents, - genTest1DaggerHiltApplicationComponent, - genTest2DaggerHiltApplicationComponent, - classSrcApp, - classSrcActivity1, - classSrcActivity2, - classSrcModule1, - classSrcModule2, - classSrcTest1, - classSrcTest2, - classGenHiltApp, - classGenHiltActivity1, - classGenHiltActivity2, - classGenAppInjector, - classGenActivityInjector1, - classGenActivityInjector2, - classGenAppInjectorDeps, - classGenActivityInjectorDeps1, - classGenActivityInjectorDeps2, - classGenModuleDeps1, - classGenModuleDeps2, - classGenComponentTreeDeps, - classGenHiltComponents, - classGenDaggerHiltApplicationComponent, - classGenTest1ComponentTreeDeps, - classGenTest2ComponentTreeDeps, - classGenTest1HiltComponents, - classGenTest2HiltComponents, - classGenTest1DaggerHiltApplicationComponent, - classGenTest2DaggerHiltApplicationComponent, - ) + val files = + listOf( + genHiltApp, + genHiltActivity1, + genHiltActivity2, + genAppInjector, + genActivityInjector1, + genActivityInjector2, + genAppInjectorDeps, + genActivityInjectorDeps1, + genActivityInjectorDeps2, + genModuleDeps1, + genModuleDeps2, + genComponentTreeDeps, + genHiltComponents, + genDaggerHiltApplicationComponent, + genTest1ComponentTreeDeps, + genTest2ComponentTreeDeps, + genTest1HiltComponents, + genTest2HiltComponents, + genTest1DaggerHiltApplicationComponent, + genTest2DaggerHiltApplicationComponent, + classSrcApp, + classSrcActivity1, + classSrcActivity2, + classSrcModule1, + classSrcModule2, + classSrcTest1, + classSrcTest2, + classGenHiltApp, + classGenHiltActivity1, + classGenHiltActivity2, + classGenAppInjector, + classGenActivityInjector1, + classGenActivityInjector2, + classGenAppInjectorDeps, + classGenActivityInjectorDeps1, + classGenActivityInjectorDeps2, + classGenModuleDeps1, + classGenModuleDeps2, + classGenComponentTreeDeps, + classGenHiltComponents, + classGenDaggerHiltApplicationComponent, + classGenTest1ComponentTreeDeps, + classGenTest2ComponentTreeDeps, + classGenTest1HiltComponents, + classGenTest2HiltComponents, + classGenTest1DaggerHiltApplicationComponent, + classGenTest2DaggerHiltApplicationComponent, + ) - fileToTimestampMap = mutableMapOf<File, Long>().apply { - for (file in files) { - this[file] = file.lastModified() + fileToTimestampMap = + mutableMapOf<File, Long>().apply { + for (file in files) { + this[file] = file.lastModified() + } } - } } private fun recordFileChanges() { - changedFiles = fileToTimestampMap.filter { (file, previousTimestamp) -> - file.exists() && file.lastModified() != previousTimestamp - }.keys + changedFiles = + fileToTimestampMap + .filter { (file, previousTimestamp) -> + file.exists() && file.lastModified() != previousTimestamp + } + .keys - unchangedFiles = fileToTimestampMap.filter { (file, previousTimestamp) -> - file.exists() && file.lastModified() == previousTimestamp - }.keys + unchangedFiles = + fileToTimestampMap + .filter { (file, previousTimestamp) -> + file.exists() && file.lastModified() == previousTimestamp + } + .keys deletedFiles = fileToTimestampMap.filter { (file, _) -> !file.exists() }.keys } private fun assertFilesExist(files: Collection<File>) { - expect.withMessage("Existing files") + expect + .withMessage("Existing files") .that(files.filter { it.exists() }) .containsExactlyElementsIn(files) } private fun assertChangedFiles(type: FileType, files: Collection<File>) { - expect.withMessage("Changed files") + expect + .withMessage("Changed files") .that(changedFiles.filter { it.name.endsWith(type.extension) }) .containsExactlyElementsIn(files) } @@ -1227,10 +1275,7 @@ class IncrementalProcessorTest(private val incapMode: String) { @JvmStatic @Parameterized.Parameters(name = "{0}") - fun parameters() = listOf( - ISOLATING_MODE, - AGGREGATING_MODE - ) + fun parameters() = listOf(ISOLATING_MODE, AGGREGATING_MODE) private const val ISOLATING_MODE = "isolating" private const val AGGREGATING_MODE = "aggregating" diff --git a/java/dagger/hilt/android/plugin/settings.gradle b/java/dagger/hilt/android/plugin/settings.gradle index 55724bf48..778bf22e4 100644 --- a/java/dagger/hilt/android/plugin/settings.gradle +++ b/java/dagger/hilt/android/plugin/settings.gradle @@ -21,3 +21,4 @@ include ':agp-wrapper-impl' include ':agp-wrapper-7-0' include ':agp-wrapper-7-1' include ':agp-wrapper-7-2' + diff --git a/java/dagger/hilt/android/processor/BUILD b/java/dagger/hilt/android/processor/BUILD index 6f45216f1..bf5a6c3b5 100644 --- a/java/dagger/hilt/android/processor/BUILD +++ b/java/dagger/hilt/android/processor/BUILD @@ -52,6 +52,7 @@ gen_maven_artifact( "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:components", "//java/dagger/hilt/processor/internal:hilt_processing_env_configs", + "//java/dagger/hilt/processor/internal:method_signature", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", diff --git a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java index 2d954481a..e37c6b7bd 100644 --- a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java +++ b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java @@ -99,6 +99,10 @@ public final class AndroidClassNames { get("dagger.hilt.android.internal.managers", "ServiceComponentManager"); public static final ClassName VIEW_COMPONENT_MANAGER = get("dagger.hilt.android.internal.managers", "ViewComponentManager"); + public static final ClassName SAVED_STATE_HANDLE_ENTRY_POINTS = + get("dagger.hilt.android.internal.managers", "SavedStateHandleEntryPoints"); + public static final ClassName SAVED_STATE_HANDLE_HOLDER = + get("dagger.hilt.android.internal.managers", "SavedStateHandleHolder"); public static final ClassName HAS_CUSTOM_INJECT = get("dagger.hilt.android.internal.migration", "HasCustomInject"); @@ -114,6 +118,10 @@ public final class AndroidClassNames { get("dagger.hilt.android.lifecycle", "HiltViewModel"); public static final ClassName HILT_VIEW_MODEL_MAP_QUALIFIER = get("dagger.hilt.android.internal.lifecycle", "HiltViewModelMap"); + + public static final ClassName HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER = + get("dagger.hilt.android.internal.lifecycle", "HiltViewModelAssistedMap"); + public static final ClassName HILT_VIEW_MODEL_KEYS_QUALIFIER = get("dagger.hilt.android.internal.lifecycle", "HiltViewModelMap", "KeySet"); public static final ClassName VIEW_MODEL = get("androidx.lifecycle", "ViewModel"); @@ -121,9 +129,13 @@ public final class AndroidClassNames { get("androidx.lifecycle", "ViewModelProvider", "Factory"); public static final ClassName SAVED_STATE_HANDLE = get("androidx.lifecycle", "SavedStateHandle"); - + public static final ClassName DEFAULT_LIFECYCLE_OBSERVER = + get("androidx.lifecycle", "DefaultLifecycleObserver"); + public static final ClassName LIFECYCLE_OWNER = get("androidx.lifecycle", "LifecycleOwner"); public static final ClassName ON_CONTEXT_AVAILABLE_LISTENER = get("androidx.activity.contextaware", "OnContextAvailableListener"); + public static final ClassName UI_THREAD = get("androidx.annotation", "UiThread"); + public static final ClassName INJECT_VIA_ON_CONTEXT_AVAILABLE_LISTENER = get("dagger.hilt.android", "InjectViaOnContextAvailableListener"); diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java index 49f439ec9..e1bf74f50 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java @@ -16,22 +16,58 @@ package dagger.hilt.android.processor.internal.androidentrypoint; +import static com.google.common.base.Preconditions.checkState; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static kotlin.streams.jdk8.StreamsKt.asStream; + import androidx.room.compiler.processing.JavaPoetExtKt; +import androidx.room.compiler.processing.XAnnotated; +import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeParameterElement; +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.hilt.android.processor.internal.AndroidClassNames; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.MethodSignature; import dagger.hilt.processor.internal.Processors; +import dagger.internal.codegen.xprocessing.XElements; import java.io.IOException; import javax.lang.model.element.Modifier; +import javax.tools.Diagnostic; /** Generates an Hilt Activity class for the @AndroidEntryPoint annotated class. */ public final class ActivityGenerator { + private enum ActivityMethod { + ON_CREATE(AndroidClassNames.BUNDLE), + ON_STOP(), + ON_DESTROY(); + + @SuppressWarnings("ImmutableEnumChecker") + private final MethodSignature signature; + + ActivityMethod(TypeName... parameterTypes) { + String methodName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name()); + this.signature = MethodSignature.of(methodName, parameterTypes); + } + } + + private static final FieldSpec SAVED_STATE_HANDLE_HOLDER_FIELD = + FieldSpec.builder(AndroidClassNames.SAVED_STATE_HANDLE_HOLDER, "savedStateHandleHolder") + .addModifiers(Modifier.PRIVATE) + .build(); + private final XProcessingEnv env; private final AndroidEntryPointMetadata metadata; private final ClassName generatedClassName; @@ -61,6 +97,13 @@ public final class ActivityGenerator { CodeBlock.builder().addStatement("_initHiltInternal()").build(), builder); builder.addMethod(init()); + if (!metadata.overridesAndroidEntryPointClass()) { + builder + .addField(SAVED_STATE_HANDLE_HOLDER_FIELD) + .addMethod(initSavedStateHandleHolderMethod()) + .addMethod(onCreateComponentActivity()) + .addMethod(onDestroyComponentActivity()); + } metadata.baseElement().getTypeParameters().stream() .map(XTypeParameterElement::getTypeVariableName) @@ -133,4 +176,119 @@ public final class ActivityGenerator { AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) .build(); } + + // @Override + // public void onCreate(Bundle bundle) { + // super.onCreate(savedInstanceState); + // initSavedStateHandleHolder(); + // } + // + private MethodSpec onCreateComponentActivity() { + XMethodElement nearestOverrideMethod = + requireNearestOverrideMethod(ActivityMethod.ON_CREATE, metadata); + if (nearestOverrideMethod.isFinal()) { + env.getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + "Do not mark onCreate as final in base Activity class, as Hilt needs to override it" + + " to inject SavedStateHandle.", + nearestOverrideMethod); + } + ParameterSpec.Builder parameterBuilder = + ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState"); + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("onCreate"); + // If the sub class is overriding onCreate with @Nullable parameter, then this generated + // method will also prefix the parameter with @Nullable. + if (isNullable(nearestOverrideMethod.getParameters().get(0))) { + parameterBuilder.addAnnotation(AndroidClassNames.NULLABLE); + } + if (nearestOverrideMethod.hasAnnotation(AndroidClassNames.UI_THREAD)) { + methodBuilder.addAnnotation(AndroidClassNames.UI_THREAD); + } + return methodBuilder + .addAnnotation(AndroidClassNames.CALL_SUPER) + .addAnnotation(Override.class) + .addModifiers(XElements.getModifiers(nearestOverrideMethod)) + .addParameter(parameterBuilder.build()) + .addStatement("super.onCreate(savedInstanceState)") + .addStatement("initSavedStateHandleHolder()") + .build(); + } + + // private void initSavedStateHandleHolder() { + // savedStateHandleHolder = componentManager().getSavedStateHandleHolder(); + // if (savedStateHandleHolder.isInvalid()) { + // savedStateHandleHolder.setExtras(getDefaultViewModelCreationExtras()); + // } + // } + private static MethodSpec initSavedStateHandleHolderMethod() { + return MethodSpec.methodBuilder("initSavedStateHandleHolder") + .addModifiers(Modifier.PRIVATE) + .beginControlFlow( + "if (getApplication() instanceof $T)", ClassNames.GENERATED_COMPONENT_MANAGER) + .addStatement( + "$N = componentManager().getSavedStateHandleHolder()", SAVED_STATE_HANDLE_HOLDER_FIELD) + .beginControlFlow("if ($N.isInvalid())", SAVED_STATE_HANDLE_HOLDER_FIELD) + .addStatement( + "$N.setExtras(getDefaultViewModelCreationExtras())", SAVED_STATE_HANDLE_HOLDER_FIELD) + .endControlFlow() + .endControlFlow() + .build(); + } + + private static boolean isNullable(XExecutableParameterElement element) { + return hasNullableAnnotation(element) || hasNullableAnnotation(element.getType()); + } + + private static boolean hasNullableAnnotation(XAnnotated element) { + return element.getAllAnnotations().stream() + .anyMatch(annotation -> annotation.getClassName().simpleName().equals("Nullable")); + } + + // @Override + // public void onDestroy() { + // super.onDestroy(); + // if (savedStateHandleHolder != null) { + // savedStateHandleHolder.clear(); + // } + // } + private MethodSpec onDestroyComponentActivity() { + XMethodElement nearestOverrideMethod = + requireNearestOverrideMethod(ActivityMethod.ON_DESTROY, metadata); + if (nearestOverrideMethod.isFinal()) { + env.getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + "Do not mark onDestroy as final in base Activity class, as Hilt needs to override it" + + " to clean up SavedStateHandle.", + nearestOverrideMethod); + } + return MethodSpec.methodBuilder("onDestroy") + .addAnnotation(Override.class) + .addModifiers(XElements.getModifiers(nearestOverrideMethod)) + .addStatement("super.onDestroy()") + .beginControlFlow("if ($N != null)", SAVED_STATE_HANDLE_HOLDER_FIELD) + .addStatement("$N.clear()", SAVED_STATE_HANDLE_HOLDER_FIELD) + .endControlFlow() + .build(); + } + + private static XMethodElement requireNearestOverrideMethod( + ActivityMethod activityMethod, AndroidEntryPointMetadata metadata) { + XMethodElement methodOnAndroidEntryPointElement = + metadata.element().getDeclaredMethods().stream() + .filter(method -> MethodSignature.of(method).equals(activityMethod.signature)) + .findFirst() + .orElse(null); + if (methodOnAndroidEntryPointElement != null) { + return methodOnAndroidEntryPointElement; + } + + ImmutableList<XMethodElement> methodOnBaseElement = + asStream(metadata.baseElement().getAllMethods()) + .filter(method -> MethodSignature.of(method).equals(activityMethod.signature)) + .collect(toImmutableList()); + checkState(methodOnBaseElement.size() >= 1); + return Iterables.getLast(methodOnBaseElement); + } } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java index 6f925dace..71ad88eee 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java @@ -23,7 +23,6 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.hilt.processor.internal.HiltCompilerOptions.isAndroidSuperClassValidationDisabled; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; -import static dagger.internal.codegen.xprocessing.XTypeElements.isKotlinSource; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import androidx.room.compiler.processing.XAnnotation; @@ -159,7 +158,7 @@ public abstract class AndroidEntryPointMetadata { public Modifier[] generatedClassModifiers() { // Note XElement#isPublic() refers to the jvm visibility. Since "internal" visibility is // represented as public in the jvm, we have to check XElement#isInternal() explicitly. - return isKotlinSource(element()) && element().isPublic() && !element().isInternal() + return element().isFromKotlin() && element().isPublic() && !element().isInternal() ? new Modifier[] {Modifier.ABSTRACT, Modifier.PUBLIC} : new Modifier[] {Modifier.ABSTRACT}; } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD index 96beff3fd..327412b52 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD @@ -71,11 +71,11 @@ java_library( "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:component_names", + "//java/dagger/hilt/processor/internal:method_signature", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/xprocessing", - "//third_party/java/auto:common", "//third_party/java/guava/base", "//third_party/java/guava/collect", "//third_party/java/javapoet", diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java index 156842bbd..78a7976d1 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java @@ -30,13 +30,12 @@ import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.XTypeParameterElement; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.hilt.android.processor.internal.AndroidClassNames; +import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; import dagger.internal.codegen.xprocessing.XExecutableTypes; import java.io.IOException; @@ -69,6 +68,13 @@ public final class BroadcastReceiverGenerator { .addModifiers(metadata.generatedClassModifiers()) .addMethod(onReceiveMethod()); + // Add an annotation used as a marker to let the bytecode injector know this receiver + // will need to be injected with a super.onReceive call. This is only necessary if no concrete + // onReceive call is implemented in any of the super classes. + if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) { + builder.addAnnotation(ClassNames.ON_RECEIVE_BYTECODE_INJECTION_MARKER); + } + JavaPoetExtKt.addOriginatingElement(builder, metadata.element()); Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); Processors.addGeneratedAnnotation(builder, env, getClass()); @@ -82,20 +88,6 @@ public final class BroadcastReceiverGenerator { Generators.copyLintAnnotations(metadata.element(), builder); Generators.copySuppressAnnotations(metadata.element(), builder); - // Add an unused field used as a marker to let the bytecode injector know this receiver will - // need to be injected with a super.onReceive call. This is only necessary if no concrete - // onReceive call is implemented in any of the super classes. - if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) { - builder.addField( - FieldSpec.builder( - TypeName.BOOLEAN, - "onReceiveBytecodeInjectionMarker", - Modifier.PRIVATE, - Modifier.FINAL) - .initializer("false") - .build()); - } - env.getFiler() .write( JavaFile.builder(generatedClassName.packageName(), builder.build()).build(), diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java index 00a64028a..b59760960 100644 --- a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java +++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java @@ -33,8 +33,6 @@ import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; -import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtil; -import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils; import dagger.internal.codegen.xprocessing.XAnnotations; import dagger.internal.codegen.xprocessing.XElements; import java.util.Collection; @@ -111,12 +109,7 @@ abstract class BindValueMetadata { XElements.toStableString(element)); XFieldElement field = asField(element); - - KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil(); - Optional<XMethodElement> propertyGetter = - metadataUtil.hasMetadata(field) - ? metadataUtil.getPropertyGetter(field) - : Optional.empty(); + Optional<XMethodElement> propertyGetter = Optional.ofNullable(field.getGetter()); if (propertyGetter.isPresent()) { ProcessorErrors.checkState( !propertyGetter.get().isPrivate(), diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD index f3686a1c8..90760ea31 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD +++ b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD @@ -38,6 +38,7 @@ kt_jvm_library( "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/xprocessing", @@ -63,6 +64,7 @@ kt_jvm_library( "//:spi", "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:dagger_models", + "//java/dagger/internal/codegen/xprocessing", "//third_party/java/auto:service", "//third_party/java/guava/graph", "//third_party/java/javapoet", diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt index 49b35a7d9..920410a0b 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt @@ -16,83 +16,181 @@ package dagger.hilt.android.processor.internal.viewmodel +import androidx.room.compiler.codegen.XTypeName +import androidx.room.compiler.codegen.toJavaPoet import androidx.room.compiler.processing.ExperimentalProcessingApi +import androidx.room.compiler.processing.XMethodElement import androidx.room.compiler.processing.XProcessingEnv import androidx.room.compiler.processing.XTypeElement import com.squareup.javapoet.ClassName import dagger.hilt.android.processor.internal.AndroidClassNames import dagger.hilt.processor.internal.ClassNames +import dagger.hilt.processor.internal.HiltCompilerOptions import dagger.hilt.processor.internal.ProcessorErrors import dagger.hilt.processor.internal.Processors import dagger.internal.codegen.xprocessing.XAnnotations +import dagger.internal.codegen.xprocessing.XElements +import dagger.internal.codegen.xprocessing.XTypeElements import dagger.internal.codegen.xprocessing.XTypes /** Data class that represents a Hilt injected ViewModel */ @OptIn(ExperimentalProcessingApi::class) -internal class ViewModelMetadata private constructor(val typeElement: XTypeElement) { - val className = typeElement.className +internal class ViewModelMetadata +private constructor(val viewModelElement: XTypeElement, val assistedFactory: XTypeElement) { + val className = viewModelElement.asClassName().toJavaPoet() + + val assistedFactoryClassName: ClassName = assistedFactory.asClassName().toJavaPoet() val modulesClassName = ClassName.get( - typeElement.packageName, + viewModelElement.packageName, "${className.simpleNames().joinToString("_")}_HiltModules" ) companion object { + + private const val ASSISTED_FACTORY_VALUE = "assistedFactory" + + fun getAssistedFactoryMethods(factory: XTypeElement?): List<XMethodElement> { + return XTypeElements.getAllNonPrivateInstanceMethods(factory) + .filter { it.isAbstract() } + .filter { !it.isJavaDefault() } + } + internal fun create( processingEnv: XProcessingEnv, - typeElement: XTypeElement, + viewModelElement: XTypeElement, ): ViewModelMetadata? { ProcessorErrors.checkState( - XTypes.isSubtype(typeElement.type, processingEnv.requireType(AndroidClassNames.VIEW_MODEL)), - typeElement, + XTypes.isSubtype( + viewModelElement.type, + processingEnv.requireType(AndroidClassNames.VIEW_MODEL) + ), + viewModelElement, "@HiltViewModel is only supported on types that subclass %s.", AndroidClassNames.VIEW_MODEL ) - typeElement - .getConstructors() - .filter { constructor -> - ProcessorErrors.checkState( - !constructor.hasAnnotation(ClassNames.ASSISTED_INJECT), - constructor, - "ViewModel constructor should be annotated with @Inject instead of @AssistedInject." - ) - constructor.hasAnnotation(ClassNames.INJECT) - } - .let { injectConstructors -> - ProcessorErrors.checkState( - injectConstructors.size == 1, - typeElement, - "@HiltViewModel annotated class should contain exactly one @Inject " + - "annotated constructor." - ) - - injectConstructors.forEach { injectConstructor -> + val isAssistedInjectFeatureEnabled = + HiltCompilerOptions.isAssistedInjectViewModelsEnabled(viewModelElement) + + val assistedFactoryType = + viewModelElement + .requireAnnotation(AndroidClassNames.HILT_VIEW_MODEL) + .getAsType(ASSISTED_FACTORY_VALUE) + val assistedFactory = assistedFactoryType.typeElement!! + + if (assistedFactoryType.asTypeName() != XTypeName.ANY_OBJECT) { + ProcessorErrors.checkState( + isAssistedInjectFeatureEnabled, + viewModelElement, + "Specified assisted factory %s for %s in @HiltViewModel but compiler option 'enableAssistedInjectViewModels' was not enabled.", + assistedFactoryType.asTypeName().toJavaPoet(), + XElements.toStableString(viewModelElement), + ) + + ProcessorErrors.checkState( + assistedFactory.hasAnnotation(ClassNames.ASSISTED_FACTORY), + viewModelElement, + "Class %s is not annotated with @AssistedFactory.", + assistedFactoryType.asTypeName().toJavaPoet() + ) + + val assistedFactoryMethod = getAssistedFactoryMethods(assistedFactory).singleOrNull() + + ProcessorErrors.checkState( + assistedFactoryMethod != null, + assistedFactory, + "Cannot find assisted factory method in %s.", + XElements.toStableString(assistedFactory) + ) + + val assistedFactoryMethodType = assistedFactoryMethod!!.asMemberOf(assistedFactoryType) + + ProcessorErrors.checkState( + assistedFactoryMethodType.returnType.asTypeName() == viewModelElement.asClassName(), + assistedFactoryMethod, + "Class %s must have a factory method that returns a %s. Found %s.", + XElements.toStableString(assistedFactory), + XElements.toStableString(viewModelElement), + XTypes.toStableString(assistedFactoryMethodType.returnType) + ) + } + + val injectConstructors = + viewModelElement.getConstructors().filter { constructor -> + if (isAssistedInjectFeatureEnabled) { + constructor.hasAnnotation(ClassNames.INJECT) || + constructor.hasAnnotation(ClassNames.ASSISTED_INJECT) + } else { ProcessorErrors.checkState( - !injectConstructor.isPrivate(), - injectConstructor, - "@Inject annotated constructors must not be private." + !constructor.hasAnnotation(ClassNames.ASSISTED_INJECT), + constructor, + "ViewModel constructor should be annotated with @Inject instead of @AssistedInject." ) + constructor.hasAnnotation(ClassNames.INJECT) } } + val injectAnnotationsMessage = + if (isAssistedInjectFeatureEnabled) { + "@Inject or @AssistedInject" + } else { + "@Inject" + } + + ProcessorErrors.checkState( + injectConstructors.size == 1, + viewModelElement, + "@HiltViewModel annotated class should contain exactly one %s annotated constructor.", + injectAnnotationsMessage + ) + + val injectConstructor = injectConstructors.single() + + ProcessorErrors.checkState( + !injectConstructor.isPrivate(), + injectConstructor, + "%s annotated constructors must not be private.", + injectAnnotationsMessage + ) + + if (injectConstructor.hasAnnotation(ClassNames.ASSISTED_INJECT)) { + // If "enableAssistedInjectViewModels" is not enabled we'll get error: + // "ViewModel constructor should be annotated with @Inject instead of @AssistedInject." + + ProcessorErrors.checkState( + assistedFactoryType.asTypeName() != XTypeName.ANY_OBJECT, + viewModelElement, + "%s must have a valid assisted factory specified in @HiltViewModel when used with assisted injection. Found %s.", + XElements.toStableString(viewModelElement), + XTypes.toStableString(assistedFactoryType) + ) + } else { + ProcessorErrors.checkState( + assistedFactoryType.asTypeName() == XTypeName.ANY_OBJECT, + injectConstructor, + "Found assisted factory %s in @HiltViewModel but the constructor was annotated with @Inject instead of @AssistedInject.", + XTypes.toStableString(assistedFactoryType), + ) + } + ProcessorErrors.checkState( - !typeElement.isNested() || typeElement.isStatic(), - typeElement, + !viewModelElement.isNested() || viewModelElement.isStatic(), + viewModelElement, "@HiltViewModel may only be used on inner classes if they are static." ) - Processors.getScopeAnnotations(typeElement).let { scopeAnnotations -> + Processors.getScopeAnnotations(viewModelElement).let { scopeAnnotations -> ProcessorErrors.checkState( scopeAnnotations.isEmpty(), - typeElement, + viewModelElement, "@HiltViewModel classes should not be scoped. Found: %s", scopeAnnotations.joinToString { XAnnotations.toStableString(it) } ) } - return ViewModelMetadata(typeElement) + return ViewModelMetadata(viewModelElement, assistedFactory) } } } diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt index 29f8c3ce5..6a156fa79 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt @@ -16,6 +16,7 @@ package dagger.hilt.android.processor.internal.viewmodel +import androidx.room.compiler.codegen.XTypeName import androidx.room.compiler.processing.ExperimentalProcessingApi import androidx.room.compiler.processing.XProcessingEnv import androidx.room.compiler.processing.addOriginatingElement @@ -23,6 +24,7 @@ import com.squareup.javapoet.AnnotationSpec import com.squareup.javapoet.ClassName import com.squareup.javapoet.JavaFile import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec import dagger.hilt.android.processor.internal.AndroidClassNames import dagger.hilt.processor.internal.ClassNames @@ -40,18 +42,20 @@ import javax.lang.model.element.Modifier * public static abstract class BindsModule { * @Binds * @IntoMap - * @StringKey("pkg.$") + * @LazyClassKey(pkg.$) * @HiltViewModelMap * public abstract ViewModel bind($ vm) * } * @Module * @InstallIn(ActivityRetainedComponent.class) * public static final class KeyModule { + * private static String className = "pkg.$"; * @Provides - * @IntoSet + * @IntoMap * @HiltViewModelMap.KeySet - * public static String provide() { - * return "pkg.$"; + * @LazyClassKey(pkg.$) + * public static boolean provide() { + * return true; * } * } * } @@ -60,20 +64,20 @@ import javax.lang.model.element.Modifier @OptIn(ExperimentalProcessingApi::class) internal class ViewModelModuleGenerator( private val processingEnv: XProcessingEnv, - private val injectedViewModel: ViewModelMetadata + private val viewModelMetadata: ViewModelMetadata, ) { fun generate() { val modulesTypeSpec = - TypeSpec.classBuilder(injectedViewModel.modulesClassName) + TypeSpec.classBuilder(viewModelMetadata.modulesClassName) .apply { - addOriginatingElement(injectedViewModel.typeElement) + addOriginatingElement(viewModelMetadata.viewModelElement) Processors.addGeneratedAnnotation(this, processingEnv, ViewModelProcessor::class.java) addAnnotation( AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT) .addMember( "topLevelClass", "$T.class", - injectedViewModel.className.topLevelClassName() + viewModelMetadata.className.topLevelClassName(), ) .build() ) @@ -85,18 +89,24 @@ internal class ViewModelModuleGenerator( .build() processingEnv.filer.write( - JavaFile.builder(injectedViewModel.modulesClassName.packageName(), modulesTypeSpec).build() + JavaFile.builder(viewModelMetadata.modulesClassName.packageName(), modulesTypeSpec).build() ) } private fun getBindsModuleTypeSpec() = createModuleTypeSpec( className = "BindsModule", - component = AndroidClassNames.VIEW_MODEL_COMPONENT + component = AndroidClassNames.VIEW_MODEL_COMPONENT, ) .addModifiers(Modifier.ABSTRACT) .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()) - .addMethod(getViewModelBindsMethod()) + .addMethod( + if (viewModelMetadata.assistedFactory.asClassName() != XTypeName.ANY_OBJECT) { + getAssistedViewModelBindsMethod() + } else { + getViewModelBindsMethod() + } + ) .build() private fun getViewModelBindsMethod() = @@ -104,20 +114,20 @@ internal class ViewModelModuleGenerator( .addAnnotation(ClassNames.BINDS) .addAnnotation(ClassNames.INTO_MAP) .addAnnotation( - AnnotationSpec.builder(ClassNames.STRING_KEY) - .addMember("value", S, injectedViewModel.className.reflectionName()) + AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY) + .addMember("value", "$T.class", viewModelMetadata.className) .build() ) .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .returns(AndroidClassNames.VIEW_MODEL) - .addParameter(injectedViewModel.className, "vm") + .addParameter(viewModelMetadata.className, "vm") .build() private fun getKeyModuleTypeSpec() = createModuleTypeSpec( className = "KeyModule", - component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT + component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT, ) .addModifiers(Modifier.FINAL) .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()) @@ -127,16 +137,49 @@ internal class ViewModelModuleGenerator( private fun getViewModelKeyProvidesMethod() = MethodSpec.methodBuilder("provide") .addAnnotation(ClassNames.PROVIDES) - .addAnnotation(ClassNames.INTO_SET) + .addAnnotation(ClassNames.INTO_MAP) + .addAnnotation( + AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY) + .addMember("value", "$T.class", viewModelMetadata.className) + .build() + ) .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_KEYS_QUALIFIER) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .returns(String::class.java) - .addStatement("return $S", injectedViewModel.className.reflectionName()) + .returns(Boolean::class.java) + .addStatement("return true") + .build() + + /** + * Should generate: + * ``` + * @Binds + * @IntoMap + * @LazyClassKey(pkg.FooViewModel.class) + * @HiltViewModelAssistedMap + * public abstract Object bind(FooViewModelAssistedFactory factory); + * ``` + * + * So that we have a HiltViewModelAssistedMap that maps from fully qualified ViewModel names to + * its assisted factory instance. + */ + private fun getAssistedViewModelBindsMethod() = + MethodSpec.methodBuilder("bind") + .addAnnotation(ClassNames.BINDS) + .addAnnotation(ClassNames.INTO_MAP) + .addAnnotation( + AnnotationSpec.builder(ClassNames.LAZY_CLASS_KEY) + .addMember("value", "$T.class", viewModelMetadata.className) + .build() + ) + .addAnnotation(AndroidClassNames.HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(viewModelMetadata.assistedFactoryClassName, "factory") + .returns(TypeName.OBJECT) .build() private fun createModuleTypeSpec(className: String, component: ClassName) = TypeSpec.classBuilder(className) - .addOriginatingElement(injectedViewModel.typeElement) + .addOriginatingElement(viewModelMetadata.viewModelElement) .addAnnotation(ClassNames.MODULE) .addAnnotation( AnnotationSpec.builder(ClassNames.INSTALL_IN) diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt index 69f7ac284..55cb289fc 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt @@ -28,16 +28,13 @@ import dagger.internal.codegen.xprocessing.XElements @OptIn(ExperimentalProcessingApi::class) /** Annotation processor for @ViewModelInject. */ class ViewModelProcessingStep(env: XProcessingEnv) : BaseProcessingStep(env) { + override fun annotationClassNames() = ImmutableSet.of(AndroidClassNames.HILT_VIEW_MODEL) override fun processEach(annotation: ClassName, element: XElement) { val typeElement = XElements.asTypeElement(element) - ViewModelMetadata.create( - processingEnv(), - typeElement, - ) - ?.let { viewModelMetadata -> - ViewModelModuleGenerator(processingEnv(), viewModelMetadata).generate() - } + ViewModelMetadata.create(processingEnv(), typeElement)?.let { viewModelMetadata -> + ViewModelModuleGenerator(processingEnv(), viewModelMetadata).generate() + } } } diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt index d5c3666b3..69094c9a5 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt @@ -14,27 +14,45 @@ * limitations under the License. */ +@file:OptIn(ExperimentalProcessingApi::class) + package dagger.hilt.android.processor.internal.viewmodel +import androidx.room.compiler.processing.ExperimentalProcessingApi +import androidx.room.compiler.processing.XMethodElement +import androidx.room.compiler.processing.XProcessingEnv +import androidx.room.compiler.processing.XProcessingEnv.Companion.create +import androidx.room.compiler.processing.XType +import androidx.room.compiler.processing.XTypeElement +import androidx.room.compiler.processing.compat.XConverters.toXProcessing import com.google.auto.service.AutoService import com.google.common.graph.EndpointPair import com.google.common.graph.ImmutableNetwork import dagger.hilt.android.processor.internal.AndroidClassNames -import dagger.hilt.processor.internal.asElement import dagger.hilt.processor.internal.getQualifiedName import dagger.hilt.processor.internal.hasAnnotation +import dagger.internal.codegen.xprocessing.XTypeElements import dagger.spi.model.Binding import dagger.spi.model.BindingGraph import dagger.spi.model.BindingGraph.Edge import dagger.spi.model.BindingGraph.Node import dagger.spi.model.BindingGraphPlugin import dagger.spi.model.BindingKind +import dagger.spi.model.DaggerProcessingEnv +import dagger.spi.model.DaggerType import dagger.spi.model.DiagnosticReporter import javax.tools.Diagnostic.Kind /** Plugin to validate users do not inject @HiltViewModel classes. */ @AutoService(BindingGraphPlugin::class) class ViewModelValidationPlugin : BindingGraphPlugin { + + private lateinit var env: XProcessingEnv + + override fun init(processingEnv: DaggerProcessingEnv, options: MutableMap<String, String>) { + env = processingEnv.toXProcessingEnv() + } + override fun visitGraph(bindingGraph: BindingGraph, diagnosticReporter: DiagnosticReporter) { if (bindingGraph.rootComponentNode().isSubcomponent()) { // This check does not work with partial graphs since it needs to take into account the source @@ -47,10 +65,10 @@ class ViewModelValidationPlugin : BindingGraphPlugin { val pair: EndpointPair<Node> = network.incidentNodes(edge) val target: Node = pair.target() val source: Node = pair.source() - if ( - target is Binding && - isHiltViewModelBinding(target) && !isInternalHiltViewModelUsage(source) - ) { + if (target !is Binding) { + return@forEach + } + if (isHiltViewModelBinding(target) && !isInternalHiltViewModelUsage(source)) { diagnosticReporter.reportDependency( Kind.ERROR, edge, @@ -59,6 +77,17 @@ class ViewModelValidationPlugin : BindingGraphPlugin { "(e.g. ViewModelProvider) instead." + "\nInjected ViewModel: ${target.key().type()}\n" ) + } else if ( + isViewModelAssistedFactory(target) && !isInternalViewModelAssistedFactoryUsage(source) + ) { + diagnosticReporter.reportDependency( + Kind.ERROR, + edge, + "\nInjection of an assisted factory for Hilt ViewModel is prohibited since it " + + "can not be used to create a ViewModel instance correctly.\nAccess the ViewModel via " + + "the Android APIs (e.g. ViewModelProvider) instead." + + "\nInjected factory: ${target.key().type()}\n" + ) } } } @@ -67,7 +96,7 @@ class ViewModelValidationPlugin : BindingGraphPlugin { // Make sure this is from an @Inject constructor rather than an overridden binding like an // @Provides and that the class is annotated with @HiltViewModel. return target.kind() == BindingKind.INJECTION && - target.key().type().asElement().hasAnnotation(AndroidClassNames.HILT_VIEW_MODEL) + target.key().type().hasAnnotation(AndroidClassNames.HILT_VIEW_MODEL) } private fun isInternalHiltViewModelUsage(source: Node): Boolean { @@ -86,4 +115,58 @@ class ViewModelValidationPlugin : BindingGraphPlugin { AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER.canonicalName() && source.key().multibindingContributionIdentifier().isPresent() } + + private fun isViewModelAssistedFactory(target: Binding): Boolean { + if (target.kind() != BindingKind.ASSISTED_FACTORY) return false + val factoryType = target.key().type() + return getAssistedInjectTypeElement(factoryType.toXType(env)) + .hasAnnotation(AndroidClassNames.HILT_VIEW_MODEL) + } + + private fun getAssistedInjectTypeElement(factoryType: XType): XTypeElement = + // The factory method and the type element for its return type cannot be + // null as the BindingGraph won't be created if the + // @AssistedFactory-annotated class is invalid. + getAssistedFactoryMethods(factoryType.typeElement) + .single() + .asMemberOf(factoryType) + .returnType + .typeElement!! + + private fun getAssistedFactoryMethods(factory: XTypeElement?): List<XMethodElement> { + return XTypeElements.getAllNonPrivateInstanceMethods(factory) + .filter { it.isAbstract() } + .filter { !it.isJavaDefault() } + } + + private fun isInternalViewModelAssistedFactoryUsage(source: Node): Boolean { + // We expect the only usage of the assisted factory for a Hilt ViewModel is in the + // code we generate: + // @Binds + // @IntoMap + // @StringKey(...) + // @HiltViewModelAssistedMap + // public abstract Object bind(FooFactory factory); + return source is Binding && + source.key().qualifier().isPresent() && + source.key().qualifier().get().getQualifiedName() == + AndroidClassNames.HILT_VIEW_MODEL_ASSISTED_FACTORY_MAP_QUALIFIER.canonicalName() && + source.key().multibindingContributionIdentifier().isPresent() + } +} + +private fun DaggerType.toXType(processingEnv: XProcessingEnv): XType { + return when (backend()) { + DaggerProcessingEnv.Backend.JAVAC -> javac().toXProcessing(processingEnv) + DaggerProcessingEnv.Backend.KSP -> ksp().toXProcessing(processingEnv) + else -> error("Backend ${ backend() } not supported yet.") + } +} + +private fun DaggerProcessingEnv.toXProcessingEnv(): XProcessingEnv { + return when (backend()) { + DaggerProcessingEnv.Backend.JAVAC -> create(javac()) + DaggerProcessingEnv.Backend.KSP -> create(ksp(), resolver()) + else -> error("Backend ${ backend() } not supported yet.") + } } diff --git a/java/dagger/hilt/android/testing/BUILD b/java/dagger/hilt/android/testing/BUILD index 6c3c6e564..ee3d78ab1 100644 --- a/java/dagger/hilt/android/testing/BUILD +++ b/java/dagger/hilt/android/testing/BUILD @@ -168,6 +168,15 @@ android_library( ], ) +android_library( + name = "skip_test_injection", + testonly = 1, + srcs = ["SkipTestInjection.java"], + deps = [ + ":package_info", + ], +) + java_library( name = "package_info", srcs = ["package-info.java"], @@ -220,6 +229,7 @@ gen_maven_artifact( artifact_target_maven_deps = [ "androidx.activity:activity", "androidx.annotation:annotation", + "androidx.annotation:annotation-experimental", "androidx.fragment:fragment", "androidx.lifecycle:lifecycle-common", "androidx.lifecycle:lifecycle-viewmodel", diff --git a/java/dagger/hilt/android/testing/SkipTestInjection.java b/java/dagger/hilt/android/testing/SkipTestInjection.java new file mode 100644 index 000000000..f8e015ba0 --- /dev/null +++ b/java/dagger/hilt/android/testing/SkipTestInjection.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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.hilt.android.testing; + +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation used for skipping test injection in a Hilt Android Test. This may be useful if + * building separate custom test infrastructure to inject the test class from another Hilt + * component. This may be used on either the test class or an annotation that annotates + * the test class. + */ +@Retention(CLASS) +@Target({ElementType.TYPE}) +public @interface SkipTestInjection {} diff --git a/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java b/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java index 8f2abd39d..89bf67882 100644 --- a/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java +++ b/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java @@ -117,7 +117,7 @@ public final class HiltCompilerTests { .collect(toMap((Processor e) -> e.getClass(), (Processor e) -> e)); // Adds extra processors, and allows overriding any processors of the same class. - extraProcessors.stream().forEach(processor -> processors.put(processor.getClass(), processor)); + extraProcessors.forEach(processor -> processors.put(processor.getClass(), processor)); return CompilerTests.compiler().withProcessors(processors.values()); } @@ -191,9 +191,9 @@ public final class HiltCompilerTests { private static ImmutableList<SymbolProcessorProvider> kspDefaultProcessors() { // TODO(bcorso): Add the rest of the KSP processors here. return ImmutableList.of( - new KspAndroidEntryPointProcessor.Provider(), - new KspAliasOfProcessor.Provider(), new KspAggregatedDepsProcessor.Provider(), + new KspAliasOfProcessor.Provider(), + new KspAndroidEntryPointProcessor.Provider(), new KspComponentProcessor.Provider(), new KspComponentTreeDepsProcessor.Provider(), new KspCustomTestApplicationProcessor.Provider(), @@ -279,16 +279,14 @@ public final class HiltCompilerTests { "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"), /* config= */ HiltProcessingEnvConfigs.CONFIGS, /* javacProcessors= */ ImmutableList.<Processor>builder() - .addAll(defaultProcessors()) - .addAll(additionalJavacProcessors()) + .addAll(mergeProcessors(defaultProcessors(), additionalJavacProcessors())) .addAll( processingSteps().stream() .map(HiltCompilerProcessors.JavacProcessor::new) .collect(toImmutableList())) .build(), /* symbolProcessorProviders= */ ImmutableList.<SymbolProcessorProvider>builder() - .addAll(kspDefaultProcessors()) - .addAll(additionalKspProcessors()) + .addAll(mergeProcessors(kspDefaultProcessors(), additionalKspProcessors())) .addAll( processingSteps().stream() .map(HiltCompilerProcessors.KspProcessor.Provider::new) @@ -300,6 +298,15 @@ public final class HiltCompilerTests { }); } + private static <T> ImmutableList<T> mergeProcessors( + Collection<T> defaultProcessors, Collection<T> extraProcessors) { + Map<Class<?>, T> processors = + defaultProcessors.stream().collect(toMap((T e) -> e.getClass(), (T e) -> e)); + // Adds extra processors, and allows overriding any processors of the same class. + extraProcessors.forEach(processor -> processors.put(processor.getClass(), processor)); + return ImmutableList.copyOf(processors.values()); + } + /** Used to build a {@link HiltCompiler}. */ @AutoValue.Builder public abstract static class Builder { diff --git a/java/dagger/hilt/migration/BUILD b/java/dagger/hilt/migration/BUILD index 446e7a2ea..27f472a18 100644 --- a/java/dagger/hilt/migration/BUILD +++ b/java/dagger/hilt/migration/BUILD @@ -18,9 +18,7 @@ package(default_visibility = ["//:src"]) java_library( name = "alias_of", - srcs = [ - "AliasOf.java", - ], + srcs = ["AliasOf.java"], exported_plugins = [ "//java/dagger/hilt/processor/internal/aliasof:processor", ], diff --git a/java/dagger/hilt/processor/BUILD b/java/dagger/hilt/processor/BUILD index ba7c9eee4..ecbc29495 100644 --- a/java/dagger/hilt/processor/BUILD +++ b/java/dagger/hilt/processor/BUILD @@ -74,6 +74,7 @@ gen_maven_artifact( "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:components", "//java/dagger/hilt/processor/internal:hilt_processing_env_configs", + "//java/dagger/hilt/processor/internal:method_signature", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", diff --git a/java/dagger/hilt/processor/internal/BUILD b/java/dagger/hilt/processor/internal/BUILD index a2746baf6..8828227aa 100644 --- a/java/dagger/hilt/processor/internal/BUILD +++ b/java/dagger/hilt/processor/internal/BUILD @@ -57,9 +57,7 @@ java_library( "ProcessorErrors.java", ], deps = [ - "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/xprocessing", - "//third_party/java/auto:common", "//third_party/java/error_prone:annotations", "//third_party/java/guava/base", "//third_party/java/guava/collect", @@ -87,6 +85,20 @@ java_library( ) java_library( + name = "method_signature", + srcs = [ + "MethodSignature.java", + ], + deps = [ + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/auto:value", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + ], +) + +java_library( name = "classnames", srcs = [ "ClassNames.java", diff --git a/java/dagger/hilt/processor/internal/ClassNames.java b/java/dagger/hilt/processor/internal/ClassNames.java index ec2e73ed4..00de786f0 100644 --- a/java/dagger/hilt/processor/internal/ClassNames.java +++ b/java/dagger/hilt/processor/internal/ClassNames.java @@ -72,7 +72,11 @@ public final class ClassNames { public static final ClassName DEFINE_COMPONENT_CLASSES = get("dagger.hilt.internal.definecomponent", "DefineComponentClasses"); + public static final ClassName IDENTIFIER_NAME_STRING = + get("dagger.internal", "IdentifierNameString"); + public static final ClassName ASSISTED_INJECT = get("dagger.assisted", "AssistedInject"); + public static final ClassName ASSISTED_FACTORY = get("dagger.assisted", "AssistedFactory"); public static final ClassName BINDS = get("dagger", "Binds"); public static final ClassName BINDS_OPTIONAL_OF = @@ -85,6 +89,7 @@ public final class ClassNames { public static final ClassName INTO_SET = get("dagger.multibindings", "IntoSet"); public static final ClassName ELEMENTS_INTO_SET = get("dagger.multibindings", "ElementsIntoSet"); public static final ClassName STRING_KEY = get("dagger.multibindings", "StringKey"); + public static final ClassName LAZY_CLASS_KEY = get("dagger.multibindings", "LazyClassKey"); public static final ClassName PROVIDES = get("dagger", "Provides"); public static final ClassName COMPONENT = get("dagger", "Component"); @@ -169,6 +174,8 @@ public final class ClassNames { get("dagger.hilt.android.internal.testing", "TestInstanceHolder"); public static final ClassName HILT_ANDROID_TEST = get("dagger.hilt.android.testing", "HiltAndroidTest"); + public static final ClassName SKIP_TEST_INJECTION = + get("dagger.hilt.android.testing", "SkipTestInjection"); public static final ClassName CUSTOM_TEST_APPLICATION = get("dagger.hilt.android.testing", "CustomTestApplication"); public static final ClassName ON_COMPONENT_READY_RUNNER = @@ -214,6 +221,9 @@ public final class ClassNames { public static final ClassName SUPPRESS_WARNINGS = get("java.lang", "SuppressWarnings"); public static final ClassName KOTLIN_SUPPRESS = get("kotlin", "Suppress"); + public static final ClassName ON_RECEIVE_BYTECODE_INJECTION_MARKER = + get("dagger.hilt.android.internal", "OnReceiveBytecodeInjectionMarker"); + // Kotlin-specific class names public static final ClassName KOTLIN_METADATA = get("kotlin", "Metadata"); diff --git a/java/dagger/hilt/processor/internal/DaggerModels.kt b/java/dagger/hilt/processor/internal/DaggerModels.kt index 7d64d2396..9fe0cf4f7 100644 --- a/java/dagger/hilt/processor/internal/DaggerModels.kt +++ b/java/dagger/hilt/processor/internal/DaggerModels.kt @@ -18,47 +18,37 @@ package dagger.hilt.processor.internal import com.google.auto.common.MoreTypes import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSDeclaration import com.squareup.javapoet.ClassName import dagger.spi.model.DaggerAnnotation import dagger.spi.model.DaggerElement import dagger.spi.model.DaggerProcessingEnv +import dagger.spi.model.DaggerProcessingEnv.Backend.JAVAC +import dagger.spi.model.DaggerProcessingEnv.Backend.KSP import dagger.spi.model.DaggerType -fun DaggerType.asElement(): DaggerElement = +fun DaggerType.hasAnnotation(className: ClassName): Boolean = when (checkNotNull(backend())) { - DaggerProcessingEnv.Backend.JAVAC -> { - val javaType = checkNotNull(java()) - DaggerElement.fromJavac(MoreTypes.asElement(javaType)) - } - DaggerProcessingEnv.Backend.KSP -> { - val kspType = checkNotNull(ksp()) - DaggerElement.fromKsp(kspType.declaration) - } + JAVAC -> Processors.hasAnnotation(MoreTypes.asTypeElement(javac()), className) + KSP -> ksp().declaration.hasAnnotation(className.canonicalName()) + } + +fun KSDeclaration.hasAnnotation(annotationName: String): Boolean = + annotations.any { + it.annotationType.resolve().declaration.qualifiedName?.asString().equals(annotationName) } fun DaggerElement.hasAnnotation(className: ClassName) = when (checkNotNull(backend())) { - DaggerProcessingEnv.Backend.JAVAC -> { - val javaElement = checkNotNull(java()) - Processors.hasAnnotation(javaElement, className) - } - DaggerProcessingEnv.Backend.KSP -> { - val kspAnnotated = checkNotNull(ksp()) - kspAnnotated.hasAnnotation(className) - } + JAVAC -> Processors.hasAnnotation(javac(), className) + KSP -> ksp().hasAnnotation(className) } fun DaggerAnnotation.getQualifiedName() = when (checkNotNull(backend())) { - DaggerProcessingEnv.Backend.JAVAC -> { - val javaAnnotation = checkNotNull(java()) - MoreTypes.asTypeElement(javaAnnotation.annotationType).qualifiedName.toString() - } - DaggerProcessingEnv.Backend.KSP -> { - val kspAnnotation = checkNotNull(ksp()) - kspAnnotation.annotationType.resolve().declaration.qualifiedName!!.asString() - } + JAVAC -> MoreTypes.asTypeElement(javac().annotationType).qualifiedName.toString() + KSP -> ksp().annotationType.resolve().declaration.qualifiedName!!.asString() } private fun KSAnnotated.hasAnnotation(className: ClassName) = diff --git a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java index cdb2d6c98..90575599b 100644 --- a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java +++ b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java @@ -21,6 +21,7 @@ import static com.google.common.base.Ascii.toUpperCase; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; +import androidx.room.compiler.processing.compat.XConverters; import com.google.common.collect.ImmutableSet; import dagger.hilt.processor.internal.optionvalues.BooleanValue; import dagger.hilt.processor.internal.optionvalues.GradleProjectType; @@ -101,6 +102,13 @@ public final class HiltCompilerOptions { return GRADLE_PROJECT_TYPE.get(env); } + public static boolean isAssistedInjectViewModelsEnabled(XTypeElement viewModelElement) { + boolean enabled = + ENABLE_ASSISTED_INJECT_VIEWMODELS.get(XConverters.getProcessingEnv(viewModelElement)) + == BooleanValue.TRUE; + return enabled; + } + /** Do not use! This is for internal use only. */ private static final EnumOption<BooleanValue> DISABLE_ANDROID_SUPERCLASS_VALIDATION = new EnumOption<>("android.internal.disableAndroidSuperclassValidation", BooleanValue.FALSE); @@ -124,6 +132,9 @@ public final class HiltCompilerOptions { private static final EnumOption<GradleProjectType> GRADLE_PROJECT_TYPE = new EnumOption<>("android.internal.projectType", GradleProjectType.UNSET); + private static final EnumOption<BooleanValue> ENABLE_ASSISTED_INJECT_VIEWMODELS = + new EnumOption<>( + "enableAssistedInjectViewModels", BooleanValue.TRUE ); private static final ImmutableSet<String> DEPRECATED_OPTIONS = ImmutableSet.of("dagger.hilt.android.useFragmentGetContextFix"); diff --git a/java/dagger/hilt/processor/internal/MethodSignature.java b/java/dagger/hilt/processor/internal/MethodSignature.java new file mode 100644 index 000000000..7fffef2bb --- /dev/null +++ b/java/dagger/hilt/processor/internal/MethodSignature.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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.hilt.processor.internal; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static java.util.stream.Collectors.joining; + +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XType; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.xprocessing.XElements; + +/** Represents the method signature needed to uniquely identify a method. */ +@AutoValue +public abstract class MethodSignature { + MethodSignature() {} + + abstract String name(); + + abstract ImmutableList<TypeName> parameters(); + + /** Creates a {@link MethodSignature} from a method name and parameter {@link TypeNames} */ + public static MethodSignature of(String methodName, TypeName... typeNames) { + return new AutoValue_MethodSignature(methodName, ImmutableList.copyOf(typeNames)); + } + + /** Creates a {@link MethodSignature} from a {@link MethodSpec} */ + public static MethodSignature of(MethodSpec method) { + return new AutoValue_MethodSignature( + method.name, method.parameters.stream().map(p -> p.type).collect(toImmutableList())); + } + + /** Creates a {@link MethodSignature} from an {@link XExecutableElement} */ + public static MethodSignature of(XExecutableElement executableElement) { + return new AutoValue_MethodSignature( + XElements.getSimpleName(executableElement), + executableElement.getParameters().stream() + .map(p -> p.getType().getTypeName()) + .collect(toImmutableList())); + } + + /** + * Creates a {@link MethodSignature} from an {@link XMethodElement}. + * + * <p>This version will resolve type parameters as declared by {@code enclosing}. + */ + static MethodSignature ofDeclaredType(XMethodElement method, XType enclosing) { + XMethodType executableType = method.asMemberOf(enclosing); + return new AutoValue_MethodSignature( + XElements.getSimpleName(method), + executableType.getParameterTypes().stream() + .map(XType::getTypeName) + .collect(toImmutableList())); + } + + /** Returns a string in the format: METHOD_NAME(PARAM_TYPE1,PARAM_TYEP2,...) */ + @Override + public final String toString() { + return String.format( + "%s(%s)", name(), parameters().stream().map(Object::toString).collect(joining(","))); + } +} diff --git a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java index 771a4b8a5..915a47de7 100644 --- a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java +++ b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java @@ -34,7 +34,6 @@ import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.ClassNames; import dagger.internal.codegen.xprocessing.XAnnotations; import dagger.internal.codegen.xprocessing.XElements; -import java.util.Optional; import javax.inject.Inject; /** Utility class for interacting with Kotlin Metadata. */ @@ -133,10 +132,6 @@ public final class KotlinMetadataUtil { : ImmutableList.of(); } - public Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) { - return metadataFactory.create(fieldElement).getPropertyGetter(fieldElement); - } - public boolean containsConstructorWithDefaultParam(XTypeElement typeElement) { return hasMetadata(typeElement) && metadataFactory.create(typeElement).containsConstructorWithDefaultParam(); diff --git a/java/dagger/hilt/processor/internal/root/RootGenerator.java b/java/dagger/hilt/processor/internal/root/RootGenerator.java index 2868f7c8f..f43327200 100644 --- a/java/dagger/hilt/processor/internal/root/RootGenerator.java +++ b/java/dagger/hilt/processor/internal/root/RootGenerator.java @@ -23,9 +23,7 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import androidx.room.compiler.processing.JavaPoetExtKt; -import androidx.room.compiler.processing.XFiler.Mode; import androidx.room.compiler.processing.XProcessingEnv; -import androidx.room.compiler.processing.XProcessingEnv.Backend; import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -132,12 +130,7 @@ final class RootGenerator { JavaFile componentsWrapperJavaFile = JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build()) .build(); - // TODO(danysantiago): Support formatting in KSP: b/288572563 - if (env.getBackend() == Backend.KSP) { - env.getFiler().write(componentsWrapperJavaFile, Mode.Isolating); - } else { - RootFileFormatter.write(componentsWrapperJavaFile, env); - } + RootFileFormatter.write(componentsWrapperJavaFile, env); } private static ComponentTree filterDescriptors(ComponentTree componentTree) { diff --git a/java/dagger/hilt/processor/internal/root/RootMetadata.java b/java/dagger/hilt/processor/internal/root/RootMetadata.java index 8166d668e..55f6e9c4e 100644 --- a/java/dagger/hilt/processor/internal/root/RootMetadata.java +++ b/java/dagger/hilt/processor/internal/root/RootMetadata.java @@ -21,7 +21,6 @@ import static com.google.common.base.Suppliers.memoize; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import androidx.room.compiler.processing.XConstructorElement; -import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; import com.google.common.base.Supplier; @@ -40,6 +39,7 @@ import javax.tools.Diagnostic; /** Contains metadata about the given hilt root. */ public final class RootMetadata { + private static final ClassName APPLICATION_CONTEXT_MODULE = ClassName.get("dagger.hilt.android.internal.modules", "ApplicationContextModule"); @@ -195,33 +195,10 @@ public final class RootMetadata { } private static boolean daggerCanConstruct(XTypeElement type) { - if (type.isKotlinObject()) { - // Treat Kotlin object modules as if Dagger can construct them (it technically can't, but it - // doesn't need to as it can use them since all their provision methods are static). + if (!Processors.requiresModuleInstance(type)) { return true; } - - return !isInnerClass(type) - && !hasNonDaggerAbstractMethod(type) - && (hasOnlyStaticProvides(type) || hasVisibleEmptyConstructor(type)); - } - - private static boolean isInnerClass(XTypeElement type) { - return type.isNested() && !type.isStatic(); - } - - private static boolean hasNonDaggerAbstractMethod(XTypeElement type) { - // TODO(erichang): Actually this isn't really supported b/28989613 - return type.getDeclaredMethods().stream() - .filter(XMethodElement::isAbstract) - .anyMatch(method -> !Processors.hasDaggerAbstractMethodAnnotation(method)); - } - - private static boolean hasOnlyStaticProvides(XTypeElement type) { - // TODO(erichang): Check for @Produces too when we have a producers story - return type.getDeclaredMethods().stream() - .filter(method -> method.hasAnnotation(ClassNames.PROVIDES)) - .allMatch(XMethodElement::isStatic); + return hasVisibleEmptyConstructor(type) && (!type.isNested() || type.isStatic()); } private static boolean hasVisibleEmptyConstructor(XTypeElement type) { diff --git a/java/dagger/hilt/processor/internal/root/RootProcessingStep.java b/java/dagger/hilt/processor/internal/root/RootProcessingStep.java index f281995d9..d6de432bc 100644 --- a/java/dagger/hilt/processor/internal/root/RootProcessingStep.java +++ b/java/dagger/hilt/processor/internal/root/RootProcessingStep.java @@ -57,6 +57,9 @@ import java.util.Set; public final class RootProcessingStep extends BaseProcessingStep { private boolean processed; + // TODO(b/297889547) do not run preProcess and postProcess if supported annotation isn't present + // in the environment. + private boolean hasElementsToProcess = false; private GeneratesRootInputs generatesRootInputs; public RootProcessingStep(XProcessingEnv env) { @@ -75,13 +78,16 @@ public final class RootProcessingStep extends BaseProcessingStep { @Override public void processEach(ClassName annotation, XElement element) throws Exception { + hasElementsToProcess = true; XTypeElement rootElement = XElements.asTypeElement(element); // TODO(bcorso): Move this logic into a separate isolating processor to avoid regenerating it // for unrelated changes in Gradle. RootType rootType = RootType.of(rootElement); if (rootType.isTestRoot()) { - new TestInjectorGenerator(processingEnv(), TestRootMetadata.of(processingEnv(), rootElement)) - .generate(); + TestRootMetadata testRootMetadata = TestRootMetadata.of(processingEnv(), rootElement); + if (testRootMetadata.skipTestInjectionAnnotation().isEmpty()) { + new TestInjectorGenerator(processingEnv(), testRootMetadata).generate(); + } } XTypeElement originatingRootElement = @@ -93,6 +99,9 @@ public final class RootProcessingStep extends BaseProcessingStep { @Override protected void postProcess(XProcessingEnv env, XRoundEnv roundEnv) throws Exception { + if (!hasElementsToProcess) { + return; + } if (!useAggregatingRootProcessor(processingEnv())) { return; } diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java index 689dae7dc..bacb75b87 100644 --- a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java +++ b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java @@ -25,6 +25,7 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import androidx.room.compiler.processing.JavaPoetExtKt; +import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XFiler.Mode; import androidx.room.compiler.processing.XProcessingEnv; @@ -41,6 +42,7 @@ import dagger.hilt.processor.internal.ComponentNames; import dagger.hilt.processor.internal.Processors; import java.io.IOException; import java.util.List; +import java.util.Optional; /** Generates an implementation of {@link dagger.hilt.android.internal.TestComponentData}. */ public final class TestComponentDataGenerator { @@ -222,6 +224,13 @@ public final class TestComponentDataGenerator { } private CodeBlock callInjectTest(XTypeElement testElement) { + Optional<XAnnotation> skipTestInjection = + rootMetadata.testRootMetadata().skipTestInjectionAnnotation(); + if (skipTestInjection.isPresent()) { + return CodeBlock.of( + "throw new IllegalStateException(\"Cannot inject test when using @$L\")", + skipTestInjection.get().getName()); + } return CodeBlock.of( "(($T) (($T) $T.getApplication($T.getApplicationContext()))" + ".generatedComponent()).injectTest(testInstance)", diff --git a/java/dagger/hilt/processor/internal/root/TestRootMetadata.java b/java/dagger/hilt/processor/internal/root/TestRootMetadata.java index 5dc6feeaa..b0523034f 100644 --- a/java/dagger/hilt/processor/internal/root/TestRootMetadata.java +++ b/java/dagger/hilt/processor/internal/root/TestRootMetadata.java @@ -16,6 +16,7 @@ package dagger.hilt.processor.internal.root; +import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; @@ -25,6 +26,8 @@ import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; import dagger.internal.codegen.xprocessing.XElements; +import java.util.Optional; +import java.util.Set; import javax.lang.model.element.TypeElement; /** Metadata class for {@code InternalTestRoot} annotated classes. */ @@ -57,6 +60,28 @@ abstract class TestRootMetadata { return Processors.append(Processors.getEnclosedClassName(testName()), "_GeneratedInjector"); } + /** + * Returns either the SkipTestInjection annotation or the first annotation that was annotated + * with SkipTestInjection, if present. + */ + Optional<XAnnotation> skipTestInjectionAnnotation() { + XAnnotation skipTestAnnotation = testElement().getAnnotation(ClassNames.SKIP_TEST_INJECTION); + if (skipTestAnnotation != null) { + return Optional.of(skipTestAnnotation); + } + + Set<XAnnotation> annotatedAnnotations = testElement().getAnnotationsAnnotatedWith( + ClassNames.SKIP_TEST_INJECTION); + if (!annotatedAnnotations.isEmpty()) { + // Just return the first annotation that skips test injection if there are multiple since + // at this point it doesn't really matter and the specific annotation is only really useful + // for communicating back to the user. + return Optional.of(annotatedAnnotations.iterator().next()); + } + + return Optional.empty(); + } + static TestRootMetadata of(XProcessingEnv env, XElement element) { XTypeElement testElement = XElements.asTypeElement(element); diff --git a/java/dagger/internal/AbstractMapFactory.java b/java/dagger/internal/AbstractMapFactory.java index 1cf83fa1d..22512e9d3 100644 --- a/java/dagger/internal/AbstractMapFactory.java +++ b/java/dagger/internal/AbstractMapFactory.java @@ -22,7 +22,6 @@ import static java.util.Collections.unmodifiableMap; import java.util.LinkedHashMap; import java.util.Map; -import javax.inject.Provider; /** * An {@code abstract} {@link Factory} implementation used to implement {@link Map} bindings. diff --git a/java/dagger/internal/DelegateFactory.java b/java/dagger/internal/DelegateFactory.java index 3b4a30f23..bc5cd9a29 100644 --- a/java/dagger/internal/DelegateFactory.java +++ b/java/dagger/internal/DelegateFactory.java @@ -17,12 +17,11 @@ package dagger.internal; import static dagger.internal.Preconditions.checkNotNull; - -import javax.inject.Provider; +import static dagger.internal.Providers.asDaggerProvider; /** * A DelegateFactory that is used to stitch Provider/Lazy indirection based dependency cycles. - * + * * @since 2.0.1 */ public final class DelegateFactory<T> implements Factory<T> { @@ -44,18 +43,43 @@ public final class DelegateFactory<T> implements Factory<T> { } /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public void setDelegatedProvider(javax.inject.Provider<T> delegate) { + setDelegatedProvider(asDaggerProvider(delegate)); + } + + /** * Sets {@code delegateFactory}'s delegate provider to {@code delegate}. * * <p>{@code delegateFactory} must be an instance of {@link DelegateFactory}, otherwise this * method will throw a {@link ClassCastException}. */ public static <T> void setDelegate(Provider<T> delegateFactory, Provider<T> delegate) { - checkNotNull(delegate); DelegateFactory<T> asDelegateFactory = (DelegateFactory<T>) delegateFactory; - if (asDelegateFactory.delegate != null) { + setDelegateInternal(asDelegateFactory, delegate); + } + + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public static <T> void setDelegate( + javax.inject.Provider<T> delegateFactory, javax.inject.Provider<T> delegate) { + DelegateFactory<T> asDelegateFactory = (DelegateFactory<T>) delegateFactory; + setDelegateInternal(asDelegateFactory, asDaggerProvider(delegate)); + } + + private static <T> void setDelegateInternal( + DelegateFactory<T> delegateFactory, Provider<T> delegate) { + checkNotNull(delegate); + if (delegateFactory.delegate != null) { throw new IllegalStateException(); } - asDelegateFactory.delegate = delegate; + delegateFactory.delegate = delegate; } /** @@ -67,4 +91,3 @@ public final class DelegateFactory<T> implements Factory<T> { return checkNotNull(delegate); } } - diff --git a/java/dagger/internal/DoubleCheck.java b/java/dagger/internal/DoubleCheck.java index af7d7f69a..6af866175 100644 --- a/java/dagger/internal/DoubleCheck.java +++ b/java/dagger/internal/DoubleCheck.java @@ -17,9 +17,9 @@ package dagger.internal; import static dagger.internal.Preconditions.checkNotNull; +import static dagger.internal.Providers.asDaggerProvider; import dagger.Lazy; -import javax.inject.Provider; /** * A {@link Lazy} and {@link Provider} implementation that memoizes the value returned from a @@ -73,7 +73,8 @@ public final class DoubleCheck<T> implements Provider<T>, Lazy<T> { /** Returns a {@link Provider} that caches the value from the given delegate provider. */ // This method is declared this way instead of "<T> Provider<T> provider(Provider<T> delegate)" // to work around an Eclipse type inference bug: https://github.com/google/dagger/issues/949. - public static <P extends Provider<T>, T> Provider<T> provider(P delegate) { + public static <P extends dagger.internal.Provider<T>, T> dagger.internal.Provider<T> provider( + P delegate) { checkNotNull(delegate); if (delegate instanceof DoubleCheck) { /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped @@ -83,6 +84,16 @@ public final class DoubleCheck<T> implements Provider<T>, Lazy<T> { return new DoubleCheck<T>(delegate); } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public static <P extends javax.inject.Provider<T>, T> javax.inject.Provider<T> provider( + P delegate) { + return provider(asDaggerProvider(delegate)); + } + /** Returns a {@link Lazy} that caches the value from the given provider. */ // This method is declared this way instead of "<T> Lazy<T> lazy(Provider<T> delegate)" // to work around an Eclipse type inference bug: https://github.com/google/dagger/issues/949. @@ -99,4 +110,12 @@ public final class DoubleCheck<T> implements Provider<T>, Lazy<T> { } return new DoubleCheck<T>(checkNotNull(provider)); } + + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + public static <P extends javax.inject.Provider<T>, T> Lazy<T> lazy(P provider) { + return lazy(asDaggerProvider(provider)); + } } diff --git a/java/dagger/internal/Factory.java b/java/dagger/internal/Factory.java index 9c03f81aa..73bcfbc13 100644 --- a/java/dagger/internal/Factory.java +++ b/java/dagger/internal/Factory.java @@ -18,7 +18,6 @@ package dagger.internal; import dagger.Provides; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Scope; /** diff --git a/java/dagger/internal/IdentifierNameString.java b/java/dagger/internal/IdentifierNameString.java new file mode 100644 index 000000000..95d2bfa6f --- /dev/null +++ b/java/dagger/internal/IdentifierNameString.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 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 static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates the dagger generated class that requires applying -identifiernamestring rule. + * + * <p>When applied, all the strings fields that corresponds to a class name within the annotated + * class will be obfuscated if its corresponding class is obfuscated. This only works with r8. + * + */ +@Documented +@Retention(CLASS) +@Target(TYPE) +public @interface IdentifierNameString {} diff --git a/java/dagger/internal/KeepFieldType.java b/java/dagger/internal/KeepFieldType.java new file mode 100644 index 000000000..090550210 --- /dev/null +++ b/java/dagger/internal/KeepFieldType.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 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 static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotates the dagger generated field that requires keeping the field types. + * + * <p>Annotating a field with this annotation, the field type's class name won't be discarded or + * obfuscated when compiles with proguard. Note:This will cause the containing class to be kept, and + * only works with proguard. + */ +@Documented +@Retention(CLASS) +@Target(FIELD) +public @interface KeepFieldType {} diff --git a/java/dagger/internal/LazyClassKeyMap.java b/java/dagger/internal/LazyClassKeyMap.java new file mode 100644 index 000000000..dabf86f91 --- /dev/null +++ b/java/dagger/internal/LazyClassKeyMap.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 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.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A class keyed map that delegates to a string keyed map under the hood. + * + * <p>A {@code LazyClassKeyMap} is created for @LazyClassKey contributed map binding. + */ +public final class LazyClassKeyMap<V> implements Map<Class<?>, V> { + private final Map<String, V> delegate; + + public static <V> Map<Class<?>, V> of(Map<String, V> delegate) { + return new LazyClassKeyMap<>(delegate); + } + + private LazyClassKeyMap(Map<String, V> delegate) { + this.delegate = delegate; + } + + @Override + public V get(Object key) { + if (!(key instanceof Class)) { + throw new IllegalArgumentException("Key must be a class"); + } + return delegate.get(((Class<?>) key).getName()); + } + + @Override + public Set<Class<?>> keySet() { + // This method will load all class keys, therefore no need to use @LazyClassKey annotated + // bindings. + throw new UnsupportedOperationException( + "Maps created with @LazyClassKey do not support usage of keySet(). Consider @ClassKey" + + " instead."); + } + + @Override + public Collection<V> values() { + return delegate.values(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof Class)) { + throw new IllegalArgumentException("Key must be a class"); + } + return delegate.containsKey(((Class<?>) key).getName()); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public Set<Map.Entry<Class<?>, V>> entrySet() { + // This method will load all class keys, therefore no need to use @LazyClassKey annotated + // bindings. + throw new UnsupportedOperationException( + "Maps created with @LazyClassKey do not support usage of entrySet(). Consider @ClassKey" + + " instead."); + } + + // The dagger map binding should be a immutable map. + @Override + public V remove(Object key) { + throw new UnsupportedOperationException("Dagger map bindings are immutable"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Dagger map bindings are immutable"); + } + + @Override + public V put(Class<?> key, V value) { + throw new UnsupportedOperationException("Dagger map bindings are immutable"); + } + + @Override + public void putAll(Map<? extends Class<?>, ? extends V> map) { + throw new UnsupportedOperationException("Dagger map bindings are immutable"); + } + + /** A factory for {@code LazyClassKeyMap}. */ + public static class Factory<V> implements Provider<Map<Class<?>, V>> { + MapFactory<String, V> delegate; + + public static <V> Factory<V> of(MapFactory<String, V> delegate) { + return new Factory<>(delegate); + } + + private Factory(MapFactory<String, V> delegate) { + this.delegate = delegate; + } + + @Override + public Map<Class<?>, V> get() { + return LazyClassKeyMap.of(delegate.get()); + } + } +} diff --git a/java/dagger/internal/MapFactory.java b/java/dagger/internal/MapFactory.java index 39748c9ad..376cfdc1c 100644 --- a/java/dagger/internal/MapFactory.java +++ b/java/dagger/internal/MapFactory.java @@ -17,12 +17,12 @@ package dagger.internal; import static dagger.internal.DaggerCollections.newLinkedHashMapWithExpectedSize; +import static dagger.internal.Providers.asDaggerProvider; import static java.util.Collections.unmodifiableMap; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; -import javax.inject.Provider; /** * A {@link Factory} implementation used to implement {@link Map} bindings. This factory returns a @@ -72,12 +72,30 @@ public final class MapFactory<K, V> extends AbstractMapFactory<K, V, V> { return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> put(K key, javax.inject.Provider<V> providerOfValue) { + return put(key, asDaggerProvider(providerOfValue)); + } + @Override public Builder<K, V> putAll(Provider<Map<K, V>> mapFactory) { super.putAll(mapFactory); return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> putAll(javax.inject.Provider<Map<K, V>> mapFactory) { + return putAll(asDaggerProvider(mapFactory)); + } + /** Returns a new {@link MapProviderFactory}. */ public MapFactory<K, V> build() { return new MapFactory<>(map); diff --git a/java/dagger/internal/MapProviderFactory.java b/java/dagger/internal/MapProviderFactory.java index 1fe478856..8491ffc85 100644 --- a/java/dagger/internal/MapProviderFactory.java +++ b/java/dagger/internal/MapProviderFactory.java @@ -16,9 +16,12 @@ package dagger.internal; +import static dagger.internal.DaggerCollections.newLinkedHashMapWithExpectedSize; +import static dagger.internal.Providers.asDaggerProvider; + import dagger.Lazy; +import java.util.Collections; import java.util.Map; -import javax.inject.Provider; /** * A {@link Factory} implementation used to implement {@link Map} bindings. This factory returns a @@ -57,12 +60,43 @@ public final class MapProviderFactory<K, V> extends AbstractMapFactory<K, V, Pro return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> put(K key, javax.inject.Provider<V> providerOfValue) { + return put(key, asDaggerProvider(providerOfValue)); + } + @Override public Builder<K, V> putAll(Provider<Map<K, Provider<V>>> mapProviderFactory) { super.putAll(mapProviderFactory); return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> putAll( + final javax.inject.Provider<Map<K, javax.inject.Provider<V>>> mapProviderFactory) { + return putAll(new Provider<Map<K, Provider<V>>>() { + @Override public Map<K, Provider<V>> get() { + Map<K, javax.inject.Provider<V>> javaxMap = mapProviderFactory.get(); + if (javaxMap.isEmpty()) { + return Collections.emptyMap(); + } + Map<K, Provider<V>> daggerMap = newLinkedHashMapWithExpectedSize(javaxMap.size()); + for (Map.Entry<K, javax.inject.Provider<V>> e : javaxMap.entrySet()) { + daggerMap.put(e.getKey(), asDaggerProvider(e.getValue())); + } + return Collections.unmodifiableMap(daggerMap); + } + }); + } + /** Returns a new {@link MapProviderFactory}. */ public MapProviderFactory<K, V> build() { return new MapProviderFactory<>(map); diff --git a/java/dagger/internal/Provider.java b/java/dagger/internal/Provider.java new file mode 100644 index 000000000..e38860187 --- /dev/null +++ b/java/dagger/internal/Provider.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 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; + +/** + * Internal Provider interface to make support for {@code javax.inject.Provider} and + * {@code jakarta.inject.Provider} easier. Do not use outside of Dagger implementation code. + */ +// TODO(erichang): Make this also extend the Jakarta Provider +public interface Provider<T> extends javax.inject.Provider<T> { +} diff --git a/java/dagger/internal/ProviderOfLazy.java b/java/dagger/internal/ProviderOfLazy.java index 23b6afd75..0430cbd6e 100644 --- a/java/dagger/internal/ProviderOfLazy.java +++ b/java/dagger/internal/ProviderOfLazy.java @@ -17,9 +17,9 @@ package dagger.internal; import static dagger.internal.Preconditions.checkNotNull; +import static dagger.internal.Providers.asDaggerProvider; import dagger.Lazy; -import javax.inject.Provider; /** * A {@link Provider} of {@link Lazy} instances that each delegate to a given {@link Provider}. @@ -51,4 +51,13 @@ public final class ProviderOfLazy<T> implements Provider<Lazy<T>> { public static <T> Provider<Lazy<T>> create(Provider<T> provider) { return new ProviderOfLazy<T>(checkNotNull(provider)); } + + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public static <T> Provider<Lazy<T>> create(javax.inject.Provider<T> provider) { + return create(asDaggerProvider(provider)); + } } diff --git a/java/dagger/internal/Providers.java b/java/dagger/internal/Providers.java new file mode 100644 index 000000000..60ec83fa4 --- /dev/null +++ b/java/dagger/internal/Providers.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 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 static dagger.internal.Preconditions.checkNotNull; + +/** Helper class for utility functions dealing with Providers. */ +public final class Providers { + + /** Converts a javax provider to a Dagger internal provider. */ + public static <T> Provider<T> asDaggerProvider(final javax.inject.Provider<T> provider) { + checkNotNull(provider); + return new Provider<T>() { + @Override public T get() { + return provider.get(); + } + }; + } + + private Providers() {} +} diff --git a/java/dagger/internal/SetFactory.java b/java/dagger/internal/SetFactory.java index 349399b3e..f16076708 100644 --- a/java/dagger/internal/SetFactory.java +++ b/java/dagger/internal/SetFactory.java @@ -20,6 +20,7 @@ import static dagger.internal.DaggerCollections.hasDuplicates; import static dagger.internal.DaggerCollections.newHashSetWithExpectedSize; import static dagger.internal.DaggerCollections.presizedList; import static dagger.internal.Preconditions.checkNotNull; +import static dagger.internal.Providers.asDaggerProvider; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableSet; @@ -27,7 +28,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; -import javax.inject.Provider; /** * A {@link Factory} implementation used to implement {@link Set} bindings. This factory always @@ -73,6 +73,15 @@ public final class SetFactory<T> implements Factory<Set<T>> { return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<T> addProvider(javax.inject.Provider<? extends T> individualProvider) { + return addProvider(asDaggerProvider(individualProvider)); + } + @SuppressWarnings("unchecked") public Builder<T> addCollectionProvider( Provider<? extends Collection<? extends T>> collectionProvider) { @@ -81,6 +90,16 @@ public final class SetFactory<T> implements Factory<Set<T>> { return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<T> addCollectionProvider( + javax.inject.Provider<? extends Collection<? extends T>> collectionProvider) { + return addCollectionProvider(asDaggerProvider(collectionProvider)); + } + public SetFactory<T> build() { assert !hasDuplicates(individualProviders) : "Codegen error? Duplicates in the provider list"; diff --git a/java/dagger/internal/SingleCheck.java b/java/dagger/internal/SingleCheck.java index 41280699d..32ba83a6f 100644 --- a/java/dagger/internal/SingleCheck.java +++ b/java/dagger/internal/SingleCheck.java @@ -17,8 +17,7 @@ package dagger.internal; import static dagger.internal.Preconditions.checkNotNull; - -import javax.inject.Provider; +import static dagger.internal.Providers.asDaggerProvider; /** * A {@link Provider} implementation that memoizes the result of another {@link Provider} using @@ -58,7 +57,7 @@ public final class SingleCheck<T> implements Provider<T> { } /** Returns a {@link Provider} that caches the value from the given delegate provider. */ - // This method is declared this way instead of "<T> Provider<T> provider(Provider<T> provider)" + // This method is declared this way instead of "<T> Provider<T> provider(Provider<T> provider)" // to work around an Eclipse type inference bug: https://github.com/google/dagger/issues/949. public static <P extends Provider<T>, T> Provider<T> provider(P provider) { // If a scoped @Binds delegates to a scoped binding, don't cache the value again. @@ -67,4 +66,13 @@ public final class SingleCheck<T> implements Provider<T> { } return new SingleCheck<T>(checkNotNull(provider)); } + + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + public static <P extends javax.inject.Provider<T>, T> javax.inject.Provider<T> provider( + P delegate) { + return provider(asDaggerProvider(delegate)); + } } diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD index a3868b529..80ca8d7dc 100644 --- a/java/dagger/internal/codegen/BUILD +++ b/java/dagger/internal/codegen/BUILD @@ -43,7 +43,6 @@ java_library( "//java/dagger/internal/codegen/compileroption", "//java/dagger/internal/codegen/componentgenerator", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/codegen/model", "//java/dagger/internal/codegen/processingstep", "//java/dagger/internal/codegen/validation", "//java/dagger/internal/codegen/writing", @@ -89,7 +88,6 @@ gen_maven_artifact( ], artifact_target_maven_deps = [ "com.google.code.findbugs:jsr305", - "com.google.dagger:dagger-producers", "com.google.dagger:dagger-spi", "com.google.dagger:dagger", "com.google.devtools.ksp:symbol-processing-api", diff --git a/java/dagger/internal/codegen/DelegateComponentProcessor.java b/java/dagger/internal/codegen/DelegateComponentProcessor.java index 96d433b40..432daa686 100644 --- a/java/dagger/internal/codegen/DelegateComponentProcessor.java +++ b/java/dagger/internal/codegen/DelegateComponentProcessor.java @@ -32,10 +32,13 @@ import dagger.Provides; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; +import dagger.internal.codegen.base.SourceFileHjarGenerator; import dagger.internal.codegen.binding.BindingGraphFactory; +import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.InjectBindingRegistry; import dagger.internal.codegen.binding.MembersInjectionBinding; import dagger.internal.codegen.binding.ModuleDescriptor; +import dagger.internal.codegen.binding.MonitoringModules; import dagger.internal.codegen.binding.ProductionBinding; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.bindinggraphvalidation.BindingGraphValidationModule; @@ -53,7 +56,6 @@ import dagger.internal.codegen.validation.InjectBindingRegistryModule; import dagger.internal.codegen.validation.InjectValidator; import dagger.internal.codegen.validation.ValidationBindingGraphPlugins; import dagger.internal.codegen.writing.FactoryGenerator; -import dagger.internal.codegen.writing.HjarSourceFileGenerator; import dagger.internal.codegen.writing.MembersInjectorGenerator; import dagger.internal.codegen.writing.ModuleGenerator; import dagger.internal.codegen.writing.ModuleProxies.ModuleConstructorProxyGenerator; @@ -98,8 +100,9 @@ final class DelegateComponentProcessor { + legacyPlugin.pluginName() + ". Either compile with KAPT or migrate the plugin to implement " + "dagger.spi.model.BindingGraphPlugin.")); - // We've already reported warnings on the invalid legacy plugins above. We can't actually - // process these plugins in KSP, so just skip them to allow processing of the valid plugins. + // Even though we've reported an error, processing will still continue for the remainder of + // the processing round to try to catch other errors. We set the javac plugins to empty to + // skip processing since it would just result in ClassCastExceptions in KSP. legacyPlugins = ImmutableSet.of(); } DaggerDelegateComponentProcessor_Injector.factory() @@ -171,6 +174,14 @@ final class DelegateComponentProcessor { @Binds @IntoSet + ClearableCache componentDescriptorFactory(ComponentDescriptor.Factory cache); + + @Binds + @IntoSet + ClearableCache monitoringModules(MonitoringModules cache); + + @Binds + @IntoSet ClearableCache bindingGraphFactory(BindingGraphFactory cache); @Binds @@ -190,34 +201,44 @@ final class DelegateComponentProcessor { interface SourceFileGeneratorsModule { @Provides static SourceFileGenerator<ProvisionBinding> factoryGenerator( - FactoryGenerator generator, CompilerOptions compilerOptions) { - return hjarWrapper(generator, compilerOptions); + FactoryGenerator generator, + CompilerOptions compilerOptions, + XProcessingEnv processingEnv) { + return hjarWrapper(generator, compilerOptions, processingEnv); } @Provides static SourceFileGenerator<ProductionBinding> producerFactoryGenerator( - ProducerFactoryGenerator generator, CompilerOptions compilerOptions) { - return hjarWrapper(generator, compilerOptions); + ProducerFactoryGenerator generator, + CompilerOptions compilerOptions, + XProcessingEnv processingEnv) { + return hjarWrapper(generator, compilerOptions, processingEnv); } @Provides static SourceFileGenerator<MembersInjectionBinding> membersInjectorGenerator( - MembersInjectorGenerator generator, CompilerOptions compilerOptions) { - return hjarWrapper(generator, compilerOptions); + MembersInjectorGenerator generator, + CompilerOptions compilerOptions, + XProcessingEnv processingEnv) { + return hjarWrapper(generator, compilerOptions, processingEnv); } @Provides @ModuleGenerator static SourceFileGenerator<XTypeElement> moduleConstructorProxyGenerator( - ModuleConstructorProxyGenerator generator, CompilerOptions compilerOptions) { - return hjarWrapper(generator, compilerOptions); + ModuleConstructorProxyGenerator generator, + CompilerOptions compilerOptions, + XProcessingEnv processingEnv) { + return hjarWrapper(generator, compilerOptions, processingEnv); } } private static <T> SourceFileGenerator<T> hjarWrapper( - SourceFileGenerator<T> generator, CompilerOptions compilerOptions) { + SourceFileGenerator<T> generator, + CompilerOptions compilerOptions, + XProcessingEnv processingEnv) { return compilerOptions.headerCompilation() - ? HjarSourceFileGenerator.wrap(generator) + ? SourceFileHjarGenerator.wrap(generator, processingEnv) : generator; } } diff --git a/java/dagger/internal/codegen/base/ElementFormatter.java b/java/dagger/internal/codegen/base/ElementFormatter.java index 16381e32f..8e3edc955 100644 --- a/java/dagger/internal/codegen/base/ElementFormatter.java +++ b/java/dagger/internal/codegen/base/ElementFormatter.java @@ -36,7 +36,8 @@ import javax.inject.Inject; * * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. * - * <p>Parameters are given with their enclosing executable, with other parameters elided. + * <p>If the element is a parameter, the returned string will include the enclosing executable, + * with other parameters elided. */ public final class ElementFormatter extends Formatter<XElement> { @Inject @@ -52,15 +53,29 @@ public final class ElementFormatter extends Formatter<XElement> { * * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. * - * <p>Parameters are given with their enclosing executable, with other parameters elided. + * <p>If the element is a parameter, the returned string will include the enclosing executable, + * with other parameters elided. */ public static String elementToString(XElement element) { + return elementToString(element, /* elideMethodParameterTypes= */ false); + } + + /** + * Returns a useful string form for an element. + * + * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. + * + * <p>Parameters are given with their enclosing executable, with other parameters elided. + */ + public static String elementToString(XElement element, boolean elideMethodParameterTypes) { if (isExecutable(element)) { return enclosingTypeAndMemberName(element) .append( - asExecutable(element).getParameters().stream() - .map(parameter -> XTypes.toStableString(parameter.getType())) - .collect(joining(", ", "(", ")"))) + elideMethodParameterTypes + ? (asExecutable(element).getParameters().isEmpty() ? "()" : "(…)") + : asExecutable(element).getParameters().stream() + .map(parameter -> XTypes.toStableString(parameter.getType())) + .collect(joining(", ", "(", ")"))) .toString(); } else if (isMethodParameter(element)) { XExecutableElement methodOrConstructor = asMethodParameter(element).getEnclosingElement(); diff --git a/java/dagger/internal/codegen/base/FrameworkTypes.java b/java/dagger/internal/codegen/base/FrameworkTypes.java index 59588caf5..e39aeabea 100644 --- a/java/dagger/internal/codegen/base/FrameworkTypes.java +++ b/java/dagger/internal/codegen/base/FrameworkTypes.java @@ -29,6 +29,7 @@ import java.util.Set; * type that the framework itself defines. */ public final class FrameworkTypes { + // TODO(erichang): Add the Jakarta Provider here private static final ImmutableSet<ClassName> PROVISION_TYPES = ImmutableSet.of(TypeNames.PROVIDER, TypeNames.LAZY, TypeNames.MEMBERS_INJECTOR); diff --git a/java/dagger/internal/codegen/base/MapType.java b/java/dagger/internal/codegen/base/MapType.java index 00401ed72..c4ba838e8 100644 --- a/java/dagger/internal/codegen/base/MapType.java +++ b/java/dagger/internal/codegen/base/MapType.java @@ -115,6 +115,13 @@ public abstract class MapType { return isMap(key.type().xprocessing()); } + public static boolean isMapOfProvider(XType keyType) { + if (MapType.isMap(keyType)) { + return MapType.from(keyType).valuesAreTypeOf(TypeNames.PROVIDER); + } + return false; + } + /** * Returns a {@link MapType} for {@code type}. * diff --git a/java/dagger/internal/codegen/base/OptionalType.java b/java/dagger/internal/codegen/base/OptionalType.java index 79b638d4a..5544eaeba 100644 --- a/java/dagger/internal/codegen/base/OptionalType.java +++ b/java/dagger/internal/codegen/base/OptionalType.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.valuesOf; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; @@ -155,4 +156,14 @@ public abstract class OptionalType { public static OptionalType from(Key key) { return from(key.type().xprocessing()); } + + public static boolean isOptionalProviderType(XType type) { + if (OptionalType.isOptional(type)) { + OptionalType optionalType = OptionalType.from(type); + if (isTypeOf(optionalType.valueType(), TypeNames.PROVIDER)) { + return true; + } + } + return false; + } } diff --git a/java/dagger/internal/codegen/base/SourceFileGenerator.java b/java/dagger/internal/codegen/base/SourceFileGenerator.java index c43499a50..dfe14b1d5 100644 --- a/java/dagger/internal/codegen/base/SourceFileGenerator.java +++ b/java/dagger/internal/codegen/base/SourceFileGenerator.java @@ -18,6 +18,7 @@ package dagger.internal.codegen.base; import static androidx.room.compiler.processing.JavaPoetExtKt.addOriginatingElement; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.CAST; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.KOTLIN_INTERNAL; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; @@ -100,13 +101,12 @@ public abstract class SourceFileGenerator<T> { .build()); generatedAnnotation.ifPresent(typeSpecBuilder::addAnnotation); - // TODO(b/134590785): Remove UNCHECKED/RAWTYPES and suppress locally where necessary. // TODO(b/263891456): Remove KOTLIN_INTERNAL and use Object/raw types where necessary. typeSpecBuilder.addAnnotation( AnnotationSpecs.suppressWarnings( ImmutableSet.<Suppression>builder() .addAll(warningSuppressions()) - .add(UNCHECKED, RAWTYPES, KOTLIN_INTERNAL) + .add(UNCHECKED, RAWTYPES, KOTLIN_INTERNAL, CAST) .build())); String packageName = closestEnclosingTypeElement(originatingElement).getPackageName(); diff --git a/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java b/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java new file mode 100644 index 000000000..6857c366f --- /dev/null +++ b/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2017 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.internal.codegen.base; + +import static com.squareup.javapoet.MethodSpec.constructorBuilder; +import static com.squareup.javapoet.MethodSpec.methodBuilder; +import static com.squareup.javapoet.TypeSpec.classBuilder; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; +import static javax.lang.model.element.Modifier.PRIVATE; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.javapoet.TypeNames; +import java.util.Optional; +import javax.lang.model.element.Modifier; + +/** + * A source file generator that only writes the relevant code necessary for Bazel to create a + * correct header (ABI) jar. + */ +public final class SourceFileHjarGenerator<T> extends SourceFileGenerator<T> { + public static <T> SourceFileGenerator<T> wrap( + SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) { + return new SourceFileHjarGenerator<>(delegate, processingEnv); + } + + private final SourceFileGenerator<T> delegate; + private final XProcessingEnv processingEnv; + + private SourceFileHjarGenerator(SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) { + super(delegate); + this.delegate = delegate; + this.processingEnv = processingEnv; + } + + @Override + public XElement originatingElement(T input) { + return delegate.originatingElement(input); + } + + @Override + public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) { + String packageName = closestEnclosingTypeElement(originatingElement(input)).getPackageName(); + return delegate.topLevelTypes(input).stream() + .map(completeType -> skeletonType(packageName, completeType.build())) + .collect(toImmutableList()); + } + + private TypeSpec.Builder skeletonType(String packageName, TypeSpec completeType) { + TypeSpec.Builder skeleton = + classBuilder(completeType.name) + .addSuperinterfaces(completeType.superinterfaces) + .addTypeVariables(completeType.typeVariables) + .addModifiers(completeType.modifiers.toArray(new Modifier[0])) + .addAnnotations(completeType.annotations); + + if (!completeType.superclass.equals(ClassName.OBJECT)) { + skeleton.superclass(completeType.superclass); + } + + completeType.methodSpecs.stream() + .filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor()) + .map(completeMethod -> skeletonMethod(packageName, completeType, completeMethod)) + .forEach(skeleton::addMethod); + + completeType.fieldSpecs.stream() + .filter(field -> !field.modifiers.contains(PRIVATE)) + .map(this::skeletonField) + .forEach(skeleton::addField); + + completeType.typeSpecs.stream() + .map(type -> skeletonType(packageName, type).build()) + .forEach(skeleton::addType); + + completeType.alwaysQualifiedNames + .forEach(skeleton::alwaysQualify); + + return skeleton; + } + + private MethodSpec skeletonMethod( + String packageName, TypeSpec completeType, MethodSpec completeMethod) { + MethodSpec.Builder skeleton = + completeMethod.isConstructor() + ? constructorBuilder() + : methodBuilder(completeMethod.name).returns(completeMethod.returnType); + + if (completeMethod.isConstructor()) { + getRequiredSuperCall(packageName, completeType) + .ifPresent(superCall -> skeleton.addStatement("$L", superCall)); + } else if (!completeMethod.returnType.equals(TypeName.VOID)) { + skeleton.addStatement("return $L", getDefaultValueCodeBlock(completeMethod.returnType)); + } + + return skeleton + .addModifiers(completeMethod.modifiers) + .addTypeVariables(completeMethod.typeVariables) + .addParameters(completeMethod.parameters) + .addExceptions(completeMethod.exceptions) + .varargs(completeMethod.varargs) + .addAnnotations(completeMethod.annotations) + .build(); + } + + private Optional<CodeBlock> getRequiredSuperCall(String packageName, TypeSpec completeType) { + if (completeType.superclass.equals(TypeName.OBJECT)) { + return Optional.empty(); + } + + ClassName rawSuperClass = (ClassName) TypeNames.rawTypeName(completeType.superclass); + XTypeElement superTypeElement = + processingEnv.requireTypeElement(rawSuperClass.canonicalName()); + + ImmutableSet<XConstructorElement> accessibleConstructors = + superTypeElement.getConstructors().stream() + .filter( + constructor -> + // isElementAccessibleFrom doesn't take protected into account so check manually + constructor.isProtected() + || isElementAccessibleFrom(constructor, packageName)) + .collect(toImmutableSet()); + + // If there's an accessible default constructor we don't need to call super() manually. + if (accessibleConstructors.isEmpty() + || accessibleConstructors.stream() + .anyMatch(constructor -> constructor.getParameters().isEmpty())) { + return Optional.empty(); + } + + return Optional.of( + CodeBlock.of( + "super($L)", + CodeBlocks.makeParametersCodeBlock( + // We just choose the first constructor (it doesn't really matter since we're just + // trying to ensure the constructor body compiles). + accessibleConstructors.stream().findFirst().get().getParameters().stream() + .map(XExecutableParameterElement::getType) + .map(XType::getTypeName) + .map(SourceFileHjarGenerator::getDefaultValueCodeBlock) + .collect(toImmutableList())))); + } + + /** + * Returns a {@link CodeBlock} containing the default value for the given {@code typeName}. + * + * <p>See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html. + */ + private static CodeBlock getDefaultValueCodeBlock(TypeName typeName) { + if (typeName.isPrimitive()) { + if (typeName.equals(TypeName.BOOLEAN)) { + return CodeBlock.of("false"); + } else if (typeName.equals(TypeName.CHAR)) { + return CodeBlock.of("'\u0000'"); + } else if (typeName.equals(TypeName.BYTE)) { + return CodeBlock.of("0"); + } else if (typeName.equals(TypeName.SHORT)) { + return CodeBlock.of("0"); + } else if (typeName.equals(TypeName.INT)) { + return CodeBlock.of("0"); + } else if (typeName.equals(TypeName.LONG)) { + return CodeBlock.of("0L"); + } else if (typeName.equals(TypeName.FLOAT)) { + return CodeBlock.of("0.0f"); + } else if (typeName.equals(TypeName.DOUBLE)) { + return CodeBlock.of("0.0d"); + } else { + throw new AssertionError("Unexpected type: " + typeName); + } + } + return CodeBlock.of("null"); + } + + private FieldSpec skeletonField(FieldSpec completeField) { + return FieldSpec.builder( + completeField.type, + completeField.name, + completeField.modifiers.toArray(new Modifier[0])) + .addAnnotations(completeField.annotations) + .build(); + } +} diff --git a/java/dagger/internal/codegen/base/TarjanSCCs.java b/java/dagger/internal/codegen/base/TarjanSCCs.java index ab9a0fdae..b089333b0 100644 --- a/java/dagger/internal/codegen/base/TarjanSCCs.java +++ b/java/dagger/internal/codegen/base/TarjanSCCs.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import static java.lang.Math.min; import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -39,7 +40,7 @@ import java.util.Set; public final class TarjanSCCs { /** Returns the set of strongly connected components in reverse topological order. */ - public static <NodeT> ImmutableSet<ImmutableSet<NodeT>> compute( + public static <NodeT> ImmutableList<ImmutableSet<NodeT>> compute( ImmutableCollection<NodeT> nodes, SuccessorsFunction<NodeT> successorsFunction) { return new TarjanSCC<>(nodes, successorsFunction).compute(); } @@ -62,14 +63,14 @@ public final class TarjanSCCs { this.lowLinks = Maps.newHashMapWithExpectedSize(nodes.size()); } - private ImmutableSet<ImmutableSet<NodeT>> compute() { + private ImmutableList<ImmutableSet<NodeT>> compute() { checkState(indexes.isEmpty(), "TarjanSCC#compute() can only be called once per instance!"); for (NodeT node : nodes) { if (!indexes.containsKey(node)) { stronglyConnect(node); } } - return ImmutableSet.copyOf(stronglyConnectedComponents); + return ImmutableList.copyOf(stronglyConnectedComponents); } private void stronglyConnect(NodeT node) { diff --git a/java/dagger/internal/codegen/binding/AnnotationExpression.java b/java/dagger/internal/codegen/binding/AnnotationExpression.java index 6980c1437..535a7ef57 100644 --- a/java/dagger/internal/codegen/binding/AnnotationExpression.java +++ b/java/dagger/internal/codegen/binding/AnnotationExpression.java @@ -112,7 +112,7 @@ public final class AnnotationExpression { } else if (value.hasAnnotationValue()) { return getAnnotationInstanceExpression(value.asAnnotation()); } else if (value.hasTypeValue()) { - return CodeBlock.of("$T.class", value.asType().getTypeName()); + return CodeBlock.of("$T.class", value.asType().getTypeElement().getClassName()); } else if (value.hasStringValue()) { return CodeBlock.of("$S", value.asString()); } else if (value.hasByteValue()) { diff --git a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java index a8bca0956..50a36b937 100644 --- a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java +++ b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java @@ -22,11 +22,11 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.xprocessing.XElements.asConstructor; import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; -import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XConstructorType; import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XHasModifiers; import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XMethodType; @@ -90,14 +90,15 @@ public final class AssistedInjectionAnnotations { } private static ImmutableList<ParameterSpec> assistedParameterSpecs( - List<? extends XVariableElement> paramElements, List<XType> paramTypes) { + List<? extends XExecutableParameterElement> paramElements, List<XType> paramTypes) { ImmutableList.Builder<ParameterSpec> assistedParameterSpecs = ImmutableList.builder(); for (int i = 0; i < paramElements.size(); i++) { - XVariableElement paramElement = paramElements.get(i); + XExecutableParameterElement paramElement = paramElements.get(i); XType paramType = paramTypes.get(i); if (isAssistedParameter(paramElement)) { assistedParameterSpecs.add( - ParameterSpec.builder(paramType.getTypeName(), getSimpleName(paramElement)).build()); + ParameterSpec.builder(paramType.getTypeName(), paramElement.getJvmName()) + .build()); } } return assistedParameterSpecs.build(); @@ -133,7 +134,7 @@ public final class AssistedInjectionAnnotations { .collect(toImmutableSet()); } - public static ImmutableList<XVariableElement> assistedParameters(Binding binding) { + public static ImmutableList<XExecutableParameterElement> assistedParameters(Binding binding) { return binding.kind() == BindingKind.ASSISTED_INJECTION ? asConstructor(binding.bindingElement().get()).getParameters().stream() .filter(AssistedInjectionAnnotations::isAssistedParameter) @@ -184,8 +185,10 @@ public final class AssistedInjectionAnnotations { public abstract ImmutableList<AssistedParameter> assistedFactoryAssistedParameters(); @Memoized - public ImmutableMap<AssistedParameter, XVariableElement> assistedInjectAssistedParametersMap() { - ImmutableMap.Builder<AssistedParameter, XVariableElement> builder = ImmutableMap.builder(); + public ImmutableMap<AssistedParameter, XExecutableParameterElement> + assistedInjectAssistedParametersMap() { + ImmutableMap.Builder<AssistedParameter, XExecutableParameterElement> builder = + ImmutableMap.builder(); for (AssistedParameter assistedParameter : assistedInjectAssistedParameters()) { builder.put(assistedParameter, assistedParameter.element()); } @@ -193,9 +196,10 @@ public final class AssistedInjectionAnnotations { } @Memoized - public ImmutableMap<AssistedParameter, XVariableElement> + public ImmutableMap<AssistedParameter, XExecutableParameterElement> assistedFactoryAssistedParametersMap() { - ImmutableMap.Builder<AssistedParameter, XVariableElement> builder = ImmutableMap.builder(); + ImmutableMap.Builder<AssistedParameter, XExecutableParameterElement> builder = + ImmutableMap.builder(); for (AssistedParameter assistedParameter : assistedFactoryAssistedParameters()) { builder.put(assistedParameter, assistedParameter.element()); } @@ -211,7 +215,8 @@ public final class AssistedInjectionAnnotations { */ @AutoValue public abstract static class AssistedParameter { - public static AssistedParameter create(XVariableElement parameter, XType parameterType) { + public static AssistedParameter create( + XExecutableParameterElement parameter, XType parameterType) { AssistedParameter assistedParameter = new AutoValue_AssistedInjectionAnnotations_AssistedParameter( Optional.ofNullable(parameter.getAnnotation(TypeNames.ASSISTED)) @@ -223,7 +228,7 @@ public final class AssistedInjectionAnnotations { return assistedParameter; } - private XVariableElement parameterElement; + private XExecutableParameterElement parameterElement; private XType parameterType; /** Returns the string qualifier from the {@link Assisted#value()}. */ @@ -237,7 +242,7 @@ public final class AssistedInjectionAnnotations { return parameterType; } - public final XVariableElement element() { + public final XExecutableParameterElement element() { return parameterElement; } @@ -260,7 +265,7 @@ public final class AssistedInjectionAnnotations { ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder(); for (int i = 0; i < assistedInjectConstructor.getParameters().size(); i++) { - XVariableElement parameter = assistedInjectConstructor.getParameters().get(i); + XExecutableParameterElement parameter = assistedInjectConstructor.getParameters().get(i); XType parameterType = assistedInjectConstructorType.getParameterTypes().get(i); if (parameter.hasAnnotation(TypeNames.ASSISTED)) { builder.add(AssistedParameter.create(parameter, parameterType)); @@ -273,7 +278,7 @@ public final class AssistedInjectionAnnotations { XMethodElement factoryMethod, XMethodType factoryMethodType) { ImmutableList.Builder<AssistedParameter> builder = ImmutableList.builder(); for (int i = 0; i < factoryMethod.getParameters().size(); i++) { - XVariableElement parameter = factoryMethod.getParameters().get(i); + XExecutableParameterElement parameter = factoryMethod.getParameters().get(i); XType parameterType = factoryMethodType.getParameterTypes().get(i); builder.add(AssistedParameter.create(parameter, parameterType)); } diff --git a/java/dagger/internal/codegen/binding/BUILD b/java/dagger/internal/codegen/binding/BUILD index e606eb414..7ba349e5b 100644 --- a/java/dagger/internal/codegen/binding/BUILD +++ b/java/dagger/internal/codegen/binding/BUILD @@ -31,11 +31,8 @@ java_library( "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/model", "//java/dagger/internal/codegen/xprocessing", - "//java/dagger/producers", - "//third_party/java/auto:common", "//third_party/java/auto:value", "//third_party/java/error_prone:annotations", "//third_party/java/guava/base", diff --git a/java/dagger/internal/codegen/binding/BindingGraph.java b/java/dagger/internal/codegen/binding/BindingGraph.java index 8dfe74f5f..0090b3d2a 100644 --- a/java/dagger/internal/codegen/binding/BindingGraph.java +++ b/java/dagger/internal/codegen/binding/BindingGraph.java @@ -149,7 +149,7 @@ public abstract class BindingGraph { /** Returns the set of strongly connected nodes in this graph in reverse topological order. */ @Memoized - public ImmutableSet<ImmutableSet<Node>> stronglyConnectedNodes() { + public ImmutableList<ImmutableSet<Node>> stronglyConnectedNodes() { return TarjanSCCs.<Node>compute( ImmutableSet.copyOf(network().nodes()), // NetworkBuilder does not have a stable successor order, so we have to roll our own @@ -212,14 +212,7 @@ public abstract class BindingGraph { // particular BindingNode. Map<Key, BindingNode> contributionBindings = new LinkedHashMap<>(); Map<Key, BindingNode> membersInjectionBindings = new LinkedHashMap<>(); - - // Construct the maps of the ContributionBindings and MembersInjectionBindings by iterating - // bindings from this component and then from each successive parent. If a binding exists in - // multple components, this order ensures that the child-most binding is always chosen first. - Stream.iterate(componentNode.componentPath(), ComponentPath::parent) - // Stream.iterate is inifinte stream so we need limit it to the known size of the path. - .limit(componentNode.componentPath().components().size()) - .flatMap(path -> topLevelBindingGraph.bindingsByComponent().get(path).stream()) + topLevelBindingGraph.bindingsByComponent().get(componentNode.componentPath()) .forEach( bindingNode -> { if (bindingNode.delegate() instanceof ContributionBinding) { @@ -233,16 +226,19 @@ public abstract class BindingGraph { BindingGraph bindingGraph = new AutoValue_BindingGraph(componentNode, topLevelBindingGraph); - ImmutableSet<ModuleDescriptor> modules = - ((ComponentNodeImpl) componentNode).componentDescriptor().modules(); + ImmutableSet<XTypeElement> modules = + ((ComponentNodeImpl) componentNode).componentDescriptor().modules().stream() + .map(ModuleDescriptor::moduleElement) + .collect(toImmutableSet()); - ImmutableSet<ModuleDescriptor> inheritedModules = + ImmutableSet<XTypeElement> inheritedModules = parent.isPresent() ? Sets.union(parent.get().ownedModules, parent.get().inheritedModules).immutableCopy() : ImmutableSet.of(); // Set these fields directly on the instance rather than passing these in as input to the // AutoValue to prevent exposing this data outside of the class. + bindingGraph.parent = parent; bindingGraph.inheritedModules = inheritedModules; bindingGraph.ownedModules = Sets.difference(modules, inheritedModules).immutableCopy(); bindingGraph.contributionBindings = ImmutableMap.copyOf(contributionBindings); @@ -257,10 +253,11 @@ public abstract class BindingGraph { return bindingGraph; } + private Optional<BindingGraph> parent; private ImmutableMap<Key, BindingNode> contributionBindings; private ImmutableMap<Key, BindingNode> membersInjectionBindings; - private ImmutableSet<ModuleDescriptor> inheritedModules; - private ImmutableSet<ModuleDescriptor> ownedModules; + private ImmutableSet<XTypeElement> inheritedModules; + private ImmutableSet<XTypeElement> ownedModules; private ImmutableSet<XTypeElement> bindingModules; BindingGraph() {} @@ -287,9 +284,7 @@ public abstract class BindingGraph { */ public final Optional<Binding> localContributionBinding(Key key) { return contributionBindings.containsKey(key) - ? Optional.of(contributionBindings.get(key)) - .filter(bindingNode -> bindingNode.componentPath().equals(componentPath())) - .map(BindingNode::delegate) + ? Optional.of(contributionBindings.get(key).delegate()) : Optional.empty(); } @@ -299,15 +294,18 @@ public abstract class BindingGraph { */ public final Optional<Binding> localMembersInjectionBinding(Key key) { return membersInjectionBindings.containsKey(key) - ? Optional.of(membersInjectionBindings.get(key)) - .filter(bindingNode -> bindingNode.componentPath().equals(componentPath())) - .map(BindingNode::delegate) + ? Optional.of(membersInjectionBindings.get(key).delegate()) : Optional.empty(); } /** Returns the {@link ContributionBinding} for the given {@link Key}. */ public final ContributionBinding contributionBinding(Key key) { - return (ContributionBinding) contributionBindings.get(key).delegate(); + if (contributionBindings.containsKey(key)) { + return (ContributionBinding) contributionBindings.get(key).delegate(); + } else if (parent.isPresent()) { + return parent.get().contributionBinding(key); + } + throw new AssertionError("Contribution binding not found for key: " + key); } /** @@ -315,9 +313,12 @@ public abstract class BindingGraph { * Optional#empty()} if one does not exist. */ public final Optional<MembersInjectionBinding> membersInjectionBinding(Key key) { - return membersInjectionBindings.containsKey(key) - ? Optional.of((MembersInjectionBinding) membersInjectionBindings.get(key).delegate()) - : Optional.empty(); + if (membersInjectionBindings.containsKey(key)) { + return Optional.of((MembersInjectionBinding) membersInjectionBindings.get(key).delegate()); + } else if (parent.isPresent()) { + return parent.get().membersInjectionBinding(key); + } + return Optional.empty(); } /** Returns the {@link XTypeElement} for the component this graph represents. */ @@ -334,9 +335,7 @@ public abstract class BindingGraph { * ancestors. */ public final ImmutableSet<XTypeElement> ownedModuleTypes() { - return ownedModules.stream() - .map(ModuleDescriptor::moduleElement) - .collect(toImmutableSet()); + return ownedModules; } /** @@ -394,7 +393,7 @@ public abstract class BindingGraph { ImmutableSet<XTypeElement> requiredModules = stream(Traverser.forTree(BindingGraph::subgraphs).depthFirstPostOrder(this)) .flatMap(graph -> graph.bindingModules.stream()) - .filter(ownedModuleTypes()::contains) + .filter(ownedModules::contains) .collect(toImmutableSet()); ImmutableSet.Builder<ComponentRequirement> requirements = ImmutableSet.builder(); componentDescriptor().requirements().stream() @@ -433,11 +432,18 @@ public abstract class BindingGraph { return topLevelBindingGraph().bindingsByComponent().get(componentPath()); } - @Memoized + // TODO(bcorso): This method can be costly. Consider removing this method and inlining it into its + // only usage, BindingGraphJsonGenerator. public ImmutableSet<BindingNode> bindingNodes() { - return ImmutableSet.<BindingNode>builder() - .addAll(contributionBindings.values()) - .addAll(membersInjectionBindings.values()) - .build(); + // Construct the set of bindings by iterating bindings from this component and then from each + // successive parent. If a binding exists in multiple components, this order ensures that the + // child-most binding is always chosen first. + Map<Key, BindingNode> bindings = new LinkedHashMap<>(); + Stream.iterate(componentPath(), ComponentPath::parent) + // Stream.iterate() is infinite stream so we need limit it to the known size of the path. + .limit(componentPath().components().size()) + .flatMap(path -> topLevelBindingGraph().bindingsByComponent().get(path).stream()) + .forEach(bindingNode -> bindings.putIfAbsent(bindingNode.key(), bindingNode)); + return ImmutableSet.copyOf(bindings.values()); } } diff --git a/java/dagger/internal/codegen/binding/BindingGraphConverter.java b/java/dagger/internal/codegen/binding/BindingGraphConverter.java index 5928b8fb0..adb4435fd 100644 --- a/java/dagger/internal/codegen/binding/BindingGraphConverter.java +++ b/java/dagger/internal/codegen/binding/BindingGraphConverter.java @@ -19,23 +19,19 @@ package dagger.internal.codegen.binding; import static com.google.common.base.Verify.verify; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.extension.DaggerGraphs.unreachableNodes; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR; -import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; import com.google.common.graph.ImmutableNetwork; import com.google.common.graph.MutableNetwork; -import com.google.common.graph.Network; import com.google.common.graph.NetworkBuilder; import dagger.internal.codegen.binding.BindingGraph.TopLevelBindingGraph; +import dagger.internal.codegen.binding.BindingGraphFactory.LegacyBindingGraph; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.model.BindingGraph.ComponentNode; import dagger.internal.codegen.model.BindingGraph.DependencyEdge; @@ -43,7 +39,6 @@ import dagger.internal.codegen.model.BindingGraph.Edge; import dagger.internal.codegen.model.BindingGraph.MissingBinding; import dagger.internal.codegen.model.BindingGraph.Node; import dagger.internal.codegen.model.ComponentPath; -import dagger.internal.codegen.model.DaggerExecutableElement; import dagger.internal.codegen.model.DaggerTypeElement; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.Key; @@ -70,7 +65,7 @@ final class BindingGraphConverter { */ BindingGraph convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph) { MutableNetwork<Node, Edge> network = asNetwork(legacyBindingGraph); - ComponentNode rootNode = rootComponentNode(network); + ComponentNode rootNode = legacyBindingGraph.componentNode(); // When bindings are copied down into child graphs because they transitively depend on local // multibindings or optional bindings, the parent-owned binding is still there. If that @@ -92,47 +87,19 @@ final class BindingGraphConverter { return converter.network; } - // TODO(dpb): Example of BindingGraph logic applied to derived networks. - private ComponentNode rootComponentNode(Network<Node, Edge> network) { - return (ComponentNode) - Iterables.find( - network.nodes(), - node -> node instanceof ComponentNode && node.componentPath().atRoot()); - } - - /** - * Used as a cache key to make sure resolved bindings are cached per component path. - * This is required so that binding nodes are not reused across different branches of the - * graph since the ResolvedBindings class only contains the component and not the path. - */ - @AutoValue - abstract static class ResolvedBindingsWithPath { - abstract ResolvedBindings resolvedBindings(); - abstract ComponentPath componentPath(); - - static ResolvedBindingsWithPath create( - ResolvedBindings resolvedBindings, ComponentPath componentPath) { - return new AutoValue_BindingGraphConverter_ResolvedBindingsWithPath( - resolvedBindings, componentPath); - } - } - private final class Converter { /** The path from the root graph to the currently visited graph. */ private final Deque<LegacyBindingGraph> bindingGraphPath = new ArrayDeque<>(); - /** The {@link ComponentPath} for each component in {@link #bindingGraphPath}. */ - private final Deque<ComponentPath> componentPaths = new ArrayDeque<>(); - private final MutableNetwork<Node, Edge> network = NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); private final Set<BindingNode> bindings = new HashSet<>(); - private final Map<ResolvedBindingsWithPath, ImmutableSet<BindingNode>> resolvedBindingsMap = + private final Map<ResolvedBindings, ImmutableSet<BindingNode>> resolvedBindingsMap = new HashMap<>(); private void visitRootComponent(LegacyBindingGraph graph) { - visitComponent(graph, null); + visitComponent(graph); } /** @@ -141,34 +108,23 @@ final class BindingGraphConverter { * <p>This implementation does the following: * * <ol> - * <li>If this component is installed in its parent by a subcomponent factory method, calls - * {@link #visitSubcomponentFactoryMethod(ComponentNode, ComponentNode, XMethodElement)}. - * <li>For each entry point in the component, calls {@link #visitEntryPoint(ComponentNode, - * DependencyRequest)}. - * <li>For each child component, calls {@link #visitComponent(LegacyBindingGraph, - * ComponentNode)}, updating the traversal state. + * <li>If this component is installed in its parent by a subcomponent factory method, adds + * an edge between the parent and child components. + * <li>For each entry point, adds an edge between the component and the entry point. + * <li>For each child component, calls {@link #visitComponent(LegacyBindingGraph)}, + * updating the traversal state. * </ol> * * @param graph the currently visited graph */ - private void visitComponent(LegacyBindingGraph graph, ComponentNode parentComponent) { + private void visitComponent(LegacyBindingGraph graph) { bindingGraphPath.addLast(graph); - ComponentPath graphPath = - ComponentPath.create( - bindingGraphPath.stream() - .map(LegacyBindingGraph::componentDescriptor) - .map(ComponentDescriptor::typeElement) - .map(DaggerTypeElement::from) - .collect(toImmutableList())); - componentPaths.addLast(graphPath); - ComponentNode currentComponent = - ComponentNodeImpl.create(componentPath(), graph.componentDescriptor()); - - network.addNode(currentComponent); + + network.addNode(graph.componentNode()); for (ComponentMethodDescriptor entryPointMethod : graph.componentDescriptor().entryPointMethods()) { - visitEntryPoint(currentComponent, entryPointMethod.dependencyRequest().get()); + addDependencyEdges(graph.componentNode(), entryPointMethod.dependencyRequest().get()); } for (ResolvedBindings resolvedBindings : graph.resolvedBindings()) { @@ -180,7 +136,7 @@ final class BindingGraphConverter { } } if (binding.kind().equals(SUBCOMPONENT_CREATOR) - && binding.componentPath().equals(currentComponent.componentPath())) { + && binding.componentPath().equals(graph.componentPath())) { network.addEdge( binding, subcomponentNode(binding.key().type().xprocessing(), graph), @@ -190,51 +146,20 @@ final class BindingGraphConverter { } } - if (bindingGraphPath.size() > 1) { - LegacyBindingGraph parent = Iterators.get(bindingGraphPath.descendingIterator(), 1); - parent + for (LegacyBindingGraph childGraph : graph.subgraphs()) { + visitComponent(childGraph); + graph .componentDescriptor() - .getFactoryMethodForChildComponent(graph.componentDescriptor()) + .getFactoryMethodForChildComponent(childGraph.componentDescriptor()) .ifPresent( childFactoryMethod -> - visitSubcomponentFactoryMethod( - parentComponent, currentComponent, childFactoryMethod.methodElement())); - } - - for (LegacyBindingGraph child : graph.subgraphs()) { - visitComponent(child, currentComponent); + network.addEdge( + graph.componentNode(), + childGraph.componentNode(), + new ChildFactoryMethodEdgeImpl(childFactoryMethod.methodElement()))); } verify(bindingGraphPath.removeLast().equals(graph)); - verify(componentPaths.removeLast().equals(graphPath)); - } - - /** - * Called once for each entry point in a component. - * - * @param componentNode the component that contains the entry point - * @param entryPoint the entry point to visit - */ - private void visitEntryPoint(ComponentNode componentNode, DependencyRequest entryPoint) { - addDependencyEdges(componentNode, entryPoint); - } - - /** - * Called if this component was installed in its parent by a subcomponent factory method. - * - * @param parentComponent the parent graph - * @param currentComponent the currently visited graph - * @param factoryMethod the factory method in the parent component that declares that the - * current component is a child - */ - private void visitSubcomponentFactoryMethod( - ComponentNode parentComponent, - ComponentNode currentComponent, - XMethodElement factoryMethod) { - network.addEdge( - parentComponent, - currentComponent, - new ChildFactoryMethodEdgeImpl(DaggerExecutableElement.from(factoryMethod))); } /** @@ -242,7 +167,7 @@ final class BindingGraphConverter { * component. */ private ComponentPath componentPath() { - return componentPaths.getLast(); + return bindingGraphPath.getLast().componentPath(); } /** @@ -250,9 +175,9 @@ final class BindingGraphConverter { * component. */ private ComponentPath pathFromRootToAncestor(XTypeElement ancestor) { - for (ComponentPath componentPath : componentPaths) { - if (componentPath.currentComponent().xprocessing().equals(ancestor)) { - return componentPath; + for (LegacyBindingGraph graph : bindingGraphPath) { + if (graph.componentDescriptor().typeElement().equals(ancestor)) { + return graph.componentPath(); } } throw new IllegalArgumentException( @@ -325,23 +250,18 @@ final class BindingGraphConverter { } private ImmutableSet<BindingNode> bindingNodes(ResolvedBindings resolvedBindings) { - ResolvedBindingsWithPath resolvedBindingsWithPath = - ResolvedBindingsWithPath.create(resolvedBindings, componentPath()); - return resolvedBindingsMap.computeIfAbsent( - resolvedBindingsWithPath, this::uncachedBindingNodes); + return resolvedBindingsMap.computeIfAbsent(resolvedBindings, this::uncachedBindingNodes); } - private ImmutableSet<BindingNode> uncachedBindingNodes( - ResolvedBindingsWithPath resolvedBindingsWithPath) { + private ImmutableSet<BindingNode> uncachedBindingNodes(ResolvedBindings resolvedBindings) { ImmutableSet.Builder<BindingNode> bindingNodes = ImmutableSet.builder(); - resolvedBindingsWithPath.resolvedBindings() + resolvedBindings .allBindings() .asMap() .forEach( (component, bindings) -> { for (Binding binding : bindings) { - bindingNodes.add( - bindingNode(resolvedBindingsWithPath.resolvedBindings(), binding, component)); + bindingNodes.add(bindingNode(resolvedBindings, binding, component)); } }); return bindingNodes.build(); diff --git a/java/dagger/internal/codegen/binding/BindingGraphFactory.java b/java/dagger/internal/codegen/binding/BindingGraphFactory.java index ba1157d50..503435901 100644 --- a/java/dagger/internal/codegen/binding/BindingGraphFactory.java +++ b/java/dagger/internal/codegen/binding/BindingGraphFactory.java @@ -37,7 +37,6 @@ import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Iterables; @@ -51,11 +50,15 @@ import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.model.BindingGraph.ComponentNode; +import dagger.internal.codegen.model.BindingKind; +import dagger.internal.codegen.model.ComponentPath; +import dagger.internal.codegen.model.DaggerTypeElement; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.Key; +import dagger.internal.codegen.model.RequestKind; import dagger.internal.codegen.model.Scope; import dagger.internal.codegen.xprocessing.XTypeElements; -import dagger.producers.internal.ProductionExecutorModule; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; @@ -182,7 +185,8 @@ public final class BindingGraphFactory implements ClearableCache { ImmutableSet.Builder<SubcomponentDeclaration> subcomponentDeclarations = ImmutableSet.builder(); // Collect transitive module bindings and multibinding declarations. - for (ModuleDescriptor moduleDescriptor : modules(componentDescriptor, parentResolver)) { + ImmutableSet<ModuleDescriptor> modules = modules(componentDescriptor, parentResolver); + for (ModuleDescriptor moduleDescriptor : modules) { explicitBindingsBuilder.addAll(moduleDescriptor.bindings()); multibindingDeclarations.addAll(moduleDescriptor.multibindingDeclarations()); subcomponentDeclarations.addAll(moduleDescriptor.subcomponentDeclarations()); @@ -190,8 +194,14 @@ public final class BindingGraphFactory implements ClearableCache { optionalsBuilder.addAll(moduleDescriptor.optionalDeclarations()); } + DaggerTypeElement component = DaggerTypeElement.from(componentDescriptor.typeElement()); + ComponentPath componentPath = + parentResolver.isPresent() + ? parentResolver.get().componentPath.childPath(component) + : ComponentPath.create(ImmutableList.of(component)); final Resolver requestResolver = new Resolver( + componentPath, parentResolver, componentDescriptor, indexBindingDeclarationsByKey(explicitBindingsBuilder.build()), @@ -214,7 +224,7 @@ public final class BindingGraphFactory implements ClearableCache { if (createFullBindingGraph) { // Resolve the keys for all bindings in all modules, stripping any multibinding contribution // identifier so that the multibinding itself is resolved. - modules(componentDescriptor, parentResolver).stream() + modules.stream() .flatMap(module -> module.allBindingKeys().stream()) .map(Key::withoutMultibindingContributionIdentifier) .forEach(requestResolver::resolve); @@ -236,11 +246,7 @@ public final class BindingGraphFactory implements ClearableCache { } } - return new LegacyBindingGraph( - componentDescriptor, - ImmutableMap.copyOf(requestResolver.getResolvedContributionBindings()), - ImmutableMap.copyOf(requestResolver.getResolvedMembersInjectionBindings()), - ImmutableList.copyOf(subgraphs.build())); + return new LegacyBindingGraph(requestResolver, subgraphs.build()); } /** @@ -254,38 +260,23 @@ public final class BindingGraphFactory implements ClearableCache { return shouldIncludeImplicitProductionModules(componentDescriptor, parentResolver) ? new ImmutableSet.Builder<ModuleDescriptor>() .addAll(componentDescriptor.modules()) - .add(descriptorForMonitoringModule(componentDescriptor.typeElement())) - .add(descriptorForProductionExecutorModule()) + .add( + moduleDescriptorFactory.create( + DaggerSuperficialValidation.requireTypeElement( + processingEnv, + generatedMonitoringModuleName(componentDescriptor.typeElement())))) + .add( + moduleDescriptorFactory.create( + processingEnv.requireTypeElement(TypeNames.PRODUCTION_EXECTUTOR_MODULE))) .build() : componentDescriptor.modules(); } private boolean shouldIncludeImplicitProductionModules( - ComponentDescriptor component, Optional<Resolver> parentResolver) { - return component.isProduction() - && ((!component.isSubcomponent() && component.isRealComponent()) - || (parentResolver.isPresent() - && !parentResolver.get().componentDescriptor.isProduction())); - } - - /** - * Returns a descriptor for a generated module that handles monitoring for production components. - * This module is generated in the {@link - * dagger.internal.codegen.validation.MonitoringModuleProcessingStep}. - * - * @throws TypeNotPresentException if the module has not been generated yet. This will cause the - * processor to retry in a later processing round. - */ - private ModuleDescriptor descriptorForMonitoringModule(XTypeElement componentDefinitionType) { - return moduleDescriptorFactory.create( - DaggerSuperficialValidation.requireTypeElement( - processingEnv, generatedMonitoringModuleName(componentDefinitionType))); - } - - /** Returns a descriptor {@link ProductionExecutorModule}. */ - private ModuleDescriptor descriptorForProductionExecutorModule() { - return moduleDescriptorFactory.create( - processingEnv.findTypeElement(TypeNames.PRODUCTION_EXECTUTOR_MODULE)); + ComponentDescriptor componentDescriptor, Optional<Resolver> parentResolver) { + return componentDescriptor.isProduction() + && componentDescriptor.isRealComponent() + && (parentResolver.isEmpty() || !parentResolver.get().componentDescriptor.isProduction()); } /** Indexes {@code bindingDeclarations} by {@link BindingDeclaration#key()}. */ @@ -299,7 +290,70 @@ public final class BindingGraphFactory implements ClearableCache { keysMatchingRequestCache.clear(); } + /** Represents a fully resolved binding graph. */ + static final class LegacyBindingGraph { + private final Resolver resolver; + private final ImmutableList<LegacyBindingGraph> resolvedSubgraphs; + private final ComponentNode componentNode; + + LegacyBindingGraph(Resolver resolver, ImmutableList<LegacyBindingGraph> resolvedSubgraphs) { + this.resolver = resolver; + this.resolvedSubgraphs = resolvedSubgraphs; + this.componentNode = + ComponentNodeImpl.create(resolver.componentPath, resolver.componentDescriptor); + } + + /** Returns the {@link ComponentNode} associated with this binding graph. */ + ComponentNode componentNode() { + return componentNode; + } + + /** Returns the {@link ComponentPath} associated with this binding graph. */ + ComponentPath componentPath() { + return resolver.componentPath; + } + + /** Returns the {@link ComponentDescriptor} associated with this binding graph. */ + ComponentDescriptor componentDescriptor() { + return resolver.componentDescriptor; + } + + /** + * Returns the {@link ResolvedBindings} in this graph or a parent graph that matches the given + * request. + * + * <p>An exception is thrown if there are no resolved bindings found for the request; however, + * this should never happen since all dependencies should have been resolved at this point. + */ + ResolvedBindings resolvedBindings(BindingRequest request) { + return request.isRequestKind(RequestKind.MEMBERS_INJECTION) + ? resolver.getResolvedMembersInjectionBindings(request.key()) + : resolver.getResolvedContributionBindings(request.key()); + } + + /** + * Returns all {@link ResolvedBindings} for the given request. + * + * <p>Note that this only returns the bindings resolved in this component. Bindings resolved in + * parent components are not included. + */ + Iterable<ResolvedBindings> resolvedBindings() { + // Don't return an immutable collection - this is only ever used for looping over all bindings + // in the graph. Copying is wasteful, especially if is a hashing collection, since the values + // should all, by definition, be distinct. + return Iterables.concat( + resolver.resolvedMembersInjectionBindings.values(), + resolver.resolvedContributionBindings.values()); + } + + /** Returns the resolved subgraphs. */ + ImmutableList<LegacyBindingGraph> subgraphs() { + return resolvedSubgraphs; + } + } + private final class Resolver { + final ComponentPath componentPath; final Optional<Resolver> parentResolver; final ComponentDescriptor componentDescriptor; final ImmutableSetMultimap<Key, ContributionBinding> explicitBindings; @@ -318,6 +372,7 @@ public final class BindingGraphFactory implements ClearableCache { final Queue<ComponentDescriptor> subcomponentsToResolve = new ArrayDeque<>(); Resolver( + ComponentPath componentPath, Optional<Resolver> parentResolver, ComponentDescriptor componentDescriptor, ImmutableSetMultimap<Key, ContributionBinding> explicitBindings, @@ -325,6 +380,7 @@ public final class BindingGraphFactory implements ClearableCache { ImmutableSetMultimap<Key, SubcomponentDeclaration> subcomponentDeclarations, ImmutableSetMultimap<Key, DelegateDeclaration> delegateDeclarations, ImmutableSetMultimap<Key, OptionalBindingDeclaration> optionalBindingDeclarations) { + this.componentPath = componentPath; this.parentResolver = parentResolver; this.componentDescriptor = checkNotNull(componentDescriptor); this.explicitBindings = checkNotNull(explicitBindings); @@ -428,6 +484,7 @@ public final class BindingGraphFactory implements ClearableCache { } return ResolvedBindings.forContributionBindings( + componentPath, requestKey, Multimaps.index(bindings, binding -> getOwningComponent(requestKey, binding)), multibindingDeclarations, @@ -466,8 +523,8 @@ public final class BindingGraphFactory implements ClearableCache { injectBindingRegistry.getOrFindMembersInjectionBinding(requestKey); return binding.isPresent() ? ResolvedBindings.forMembersInjectionBinding( - requestKey, componentDescriptor, binding.get()) - : ResolvedBindings.noBindings(requestKey); + componentPath, requestKey, componentDescriptor, binding.get()) + : ResolvedBindings.noBindings(componentPath, requestKey); } /** @@ -577,8 +634,7 @@ public final class BindingGraphFactory implements ClearableCache { * ResolvedBindings#owningComponent(ContributionBinding)}. */ private XTypeElement getOwningComponent(Key requestKey, ContributionBinding binding) { - if (isResolvedInParent(requestKey, binding) - && !new LocalDependencyChecker().dependsOnLocalBindings(binding)) { + if (isResolvedInParent(requestKey, binding) && !requiresResolution(binding)) { ResolvedBindings parentResolvedBindings = parentResolver.get().resolvedContributionBindings.get(requestKey); return parentResolvedBindings.owningComponent(binding); @@ -799,11 +855,17 @@ public final class BindingGraphFactory implements ClearableCache { /* Resolve in the parent in case there are multibinding contributions or conflicts in some * component between this one and the previously-resolved one. */ parentResolver.get().resolve(key); - if (!new LocalDependencyChecker().dependsOnLocalBindings(key) - && getLocalExplicitBindings(key).isEmpty()) { + ResolvedBindings previouslyResolvedBindings = getPreviouslyResolvedBindings(key).get(); + // TODO(b/305748522): Allow caching for assisted injection bindings. + boolean isAssistedInjectionBinding = + previouslyResolvedBindings.bindings().stream() + .anyMatch(binding -> binding.kind() == BindingKind.ASSISTED_INJECTION); + if (!isAssistedInjectionBinding + && !requiresResolution(key) + && getLocalExplicitBindings(key).isEmpty()) { /* Cache the inherited parent component's bindings in case resolving at the parent found * bindings in some component between this one and the previously-resolved one. */ - resolvedContributionBindings.put(key, getPreviouslyResolvedBindings(key).get()); + resolvedContributionBindings.put(key, previouslyResolvedBindings); return; } } @@ -830,26 +892,29 @@ public final class BindingGraphFactory implements ClearableCache { } } - /** - * Returns all of the {@link ResolvedBindings} for {@link ContributionBinding}s from this and - * all ancestor resolvers, indexed by {@link ResolvedBindings#key()}. - */ - Map<Key, ResolvedBindings> getResolvedContributionBindings() { - Map<Key, ResolvedBindings> bindings = new LinkedHashMap<>(); - parentResolver.ifPresent(parent -> bindings.putAll(parent.getResolvedContributionBindings())); - bindings.putAll(resolvedContributionBindings); - return bindings; + private ResolvedBindings getResolvedContributionBindings(Key key) { + if (resolvedContributionBindings.containsKey(key)) { + return resolvedContributionBindings.get(key); + } + if (parentResolver.isPresent()) { + return parentResolver.get().getResolvedContributionBindings(key); + } + throw new AssertionError("No resolved bindings for key: " + key); } - /** - * Returns all of the {@link ResolvedBindings} for {@link MembersInjectionBinding} from this - * resolvers, indexed by {@link ResolvedBindings#key()}. - */ - ImmutableMap<Key, ResolvedBindings> getResolvedMembersInjectionBindings() { - return ImmutableMap.copyOf(resolvedMembersInjectionBindings); + private ResolvedBindings getResolvedMembersInjectionBindings(Key key) { + return resolvedMembersInjectionBindings.get(key); } - private final class LocalDependencyChecker { + private boolean requiresResolution(Key key) { + return new LegacyRequiresResolutionChecker().requiresResolution(key); + } + + private boolean requiresResolution(Binding binding) { + return new LegacyRequiresResolutionChecker().requiresResolution(binding); + } + + private final class LegacyRequiresResolutionChecker { private final Set<Object> cycleChecker = new HashSet<>(); /** @@ -863,14 +928,14 @@ public final class BindingGraphFactory implements ClearableCache { * * @throws IllegalArgumentException if {@link #getPreviouslyResolvedBindings(Key)} is empty */ - private boolean dependsOnLocalBindings(Key key) { + private boolean requiresResolution(Key key) { // Don't recur infinitely if there are valid cycles in the dependency graph. // http://b/23032377 if (!cycleChecker.add(key)) { return false; } return reentrantComputeIfAbsent( - keyDependsOnLocalBindingsCache, key, this::dependsOnLocalBindingsUncached); + keyDependsOnLocalBindingsCache, key, this::requiresResolutionUncached); } /** @@ -882,75 +947,89 @@ public final class BindingGraphFactory implements ClearableCache { * <p>We don't care about non-reusable scoped dependencies because they will never depend on * multibindings with contributions from subcomponents. */ - private boolean dependsOnLocalBindings(Binding binding) { + private boolean requiresResolution(Binding binding) { if (!cycleChecker.add(binding)) { return false; } return reentrantComputeIfAbsent( - bindingDependsOnLocalBindingsCache, binding, this::dependsOnLocalBindingsUncached); + bindingDependsOnLocalBindingsCache, binding, this::requiresResolutionUncached); } - private boolean dependsOnLocalBindingsUncached(Key key) { + private boolean requiresResolutionUncached(Key key) { checkArgument( getPreviouslyResolvedBindings(key).isPresent(), "no previously resolved bindings in %s for %s", Resolver.this, key); ResolvedBindings previouslyResolvedBindings = getPreviouslyResolvedBindings(key).get(); - if (hasLocalMultibindingContributions(key) - || hasLocalOptionalBindingContribution(previouslyResolvedBindings)) { + if (hasLocalBindings(previouslyResolvedBindings)) { return true; } for (Binding binding : previouslyResolvedBindings.bindings()) { - if (dependsOnLocalBindings(binding)) { + if (requiresResolution(binding)) { return true; } } return false; } - private boolean dependsOnLocalBindingsUncached(Binding binding) { + private boolean requiresResolutionUncached(Binding binding) { if ((!binding.scope().isPresent() || binding.scope().get().isReusable()) // TODO(beder): Figure out what happens with production subcomponents. && !binding.bindingType().equals(BindingType.PRODUCTION)) { for (DependencyRequest dependency : binding.dependencies()) { - if (dependsOnLocalBindings(dependency.key())) { + if (requiresResolution(dependency.key())) { return true; } } } return false; } + } - /** - * Returns {@code true} if there is at least one multibinding contribution declared within - * this component's modules that matches the key. - */ - private boolean hasLocalMultibindingContributions(Key requestKey) { - return keysMatchingRequest(requestKey) - .stream() - .anyMatch(key -> !getLocalExplicitMultibindings(key).isEmpty()); - } + private boolean hasLocalBindings(Binding binding) { + return hasLocalMultibindingContributions(binding.key()) + || hasLocalOptionalBindingContribution( + binding.key(), ImmutableSet.of((ContributionBinding) binding)); + } - /** - * Returns {@code true} if there is a contribution in this component for an {@code - * Optional<Foo>} key that has not been contributed in a parent. - */ - private boolean hasLocalOptionalBindingContribution(ResolvedBindings resolvedBindings) { - if (resolvedBindings - .contributionBindings() - .stream() - .map(ContributionBinding::kind) - .anyMatch(isEqual(OPTIONAL))) { - return !getLocalExplicitBindings(keyFactory.unwrapOptional(resolvedBindings.key()).get()) - .isEmpty(); - } else { - // If a parent contributes a @Provides Optional<Foo> binding and a child has a - // @BindsOptionalOf Foo method, the two should conflict, even if there is no binding for - // Foo on its own - return !getOptionalBindingDeclarations(resolvedBindings.key()).isEmpty(); - } + private boolean hasLocalBindings(ResolvedBindings resolvedBindings) { + return hasLocalMultibindingContributions(resolvedBindings.key()) + || hasLocalOptionalBindingContribution(resolvedBindings); + } + + /** + * Returns {@code true} if there is at least one multibinding contribution declared within + * this component's modules that matches the key. + */ + private boolean hasLocalMultibindingContributions(Key requestKey) { + return keysMatchingRequest(requestKey) + .stream() + .anyMatch(key -> !getLocalExplicitMultibindings(key).isEmpty()); + } + + /** + * Returns {@code true} if there is a contribution in this component for an {@code + * Optional<Foo>} key that has not been contributed in a parent. + */ + private boolean hasLocalOptionalBindingContribution(ResolvedBindings resolvedBindings) { + return hasLocalOptionalBindingContribution( + resolvedBindings.key(), resolvedBindings.contributionBindings()); + } + + private boolean hasLocalOptionalBindingContribution( + Key key, ImmutableSet<ContributionBinding> previousContributionBindings) { + if (previousContributionBindings.stream() + .map(ContributionBinding::kind) + .anyMatch(isEqual(OPTIONAL))) { + return !getLocalExplicitBindings(keyFactory.unwrapOptional(key).get()) + .isEmpty(); + } else { + // If a parent contributes a @Provides Optional<Foo> binding and a child has a + // @BindsOptionalOf Foo method, the two should conflict, even if there is no binding for + // Foo on its own + return !getOptionalBindingDeclarations(key).isEmpty(); } } } diff --git a/java/dagger/internal/codegen/binding/CancellationPolicy.java b/java/dagger/internal/codegen/binding/CancellationPolicy.java new file mode 100644 index 000000000..7be42ffbb --- /dev/null +++ b/java/dagger/internal/codegen/binding/CancellationPolicy.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 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.binding; + +import static com.google.common.base.Preconditions.checkArgument; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; + +import androidx.room.compiler.processing.XAnnotation; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.xprocessing.XAnnotations; + +/** + * The cancellation policy for a {@link dagger.producers.ProductionComponent}. + * + * <p>@see dagger.producers.CancellationPolicy + */ +public enum CancellationPolicy { + PROPAGATE, + IGNORE; + + static CancellationPolicy from(XAnnotation annotation) { + checkArgument(XAnnotations.getClassName(annotation).equals(TypeNames.CANCELLATION_POLICY)); + return valueOf(getSimpleName(annotation.getAsEnum("fromSubcomponents"))); + } +} diff --git a/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java b/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java index 077f4546a..4b1e15ede 100644 --- a/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java +++ b/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java @@ -18,6 +18,7 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.base.ElementFormatter.elementToString; +import androidx.room.compiler.processing.XMethodElement; import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge; import dagger.internal.codegen.model.DaggerExecutableElement; @@ -26,8 +27,8 @@ public final class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge private final DaggerExecutableElement factoryMethod; - ChildFactoryMethodEdgeImpl(DaggerExecutableElement factoryMethod) { - this.factoryMethod = factoryMethod; + ChildFactoryMethodEdgeImpl(XMethodElement factoryMethod) { + this.factoryMethod = DaggerExecutableElement.from(factoryMethod); } @Override diff --git a/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java b/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java index 0bc22099a..254ca7ea5 100644 --- a/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java +++ b/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java @@ -41,6 +41,7 @@ import dagger.internal.codegen.base.ComponentCreatorAnnotation; import dagger.internal.codegen.base.ComponentCreatorKind; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DependencyRequest; +import dagger.internal.codegen.xprocessing.XElements; import java.util.List; /** @@ -161,7 +162,12 @@ public abstract class ComponentCreatorDescriptor { for (XMethodElement method : getAllUnimplementedMethods(creator)) { XMethodType resolvedMethodType = method.asMemberOf(creator.getType()); if (isSubtype(componentType, resolvedMethodType.getReturnType())) { - verify(factoryMethod == null); // validation should have ensured there's only 1. + verify( + factoryMethod == null, + "Expected a single factory method for %s but found multiple: [%s, %s]", + XElements.toStableString(creator), + XElements.toStableString(factoryMethod), + XElements.toStableString(method)); factoryMethod = method; } else { XExecutableParameterElement parameter = getOnlyElement(method.getParameters()); @@ -171,7 +177,10 @@ public abstract class ComponentCreatorDescriptor { method); } } - verify(factoryMethod != null); // validation should have ensured this. + verify( + factoryMethod != null, + "Expected a single factory method for %s but found none.", + XElements.toStableString(creator)); ImmutableSetMultimap.Builder<ComponentRequirement, XExecutableParameterElement> factoryParameters = ImmutableSetMultimap.builder(); diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptor.java b/java/dagger/internal/codegen/binding/ComponentDescriptor.java index a105608b7..b0a0183b9 100644 --- a/java/dagger/internal/codegen/binding/ComponentDescriptor.java +++ b/java/dagger/internal/codegen/binding/ComponentDescriptor.java @@ -21,14 +21,28 @@ import static androidx.room.compiler.processing.XTypeKt.isVoid; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotation; +import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; +import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations; +import static dagger.internal.codegen.base.ComponentCreatorAnnotation.creatorAnnotationsFor; +import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; +import static dagger.internal.codegen.base.Scopes.productionScope; +import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; +import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes; +import static dagger.internal.codegen.binding.ConfigurationAnnotations.isSubcomponentCreator; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.javapoet.TypeNames.isFutureType; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; +import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; -import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XMethodType; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; @@ -44,16 +58,21 @@ import com.squareup.javapoet.TypeName; import dagger.Component; import dagger.Module; import dagger.Subcomponent; +import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.ComponentAnnotation; +import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.ModuleAnnotation; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.Scope; -import dagger.internal.codegen.xprocessing.XAnnotations; +import dagger.internal.codegen.xprocessing.XTypeElements; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import javax.inject.Inject; +import javax.inject.Singleton; /** * A component declaration. @@ -68,52 +87,54 @@ import java.util.stream.Stream; @CheckReturnValue @AutoValue public abstract class ComponentDescriptor { + /** The annotation that specifies that {@link #typeElement()} is a component. */ + public abstract ComponentAnnotation annotation(); + /** - * The cancellation policy for a {@link dagger.producers.ProductionComponent}. - * - * <p>@see dagger.producers.CancellationPolicy + * The element that defines the component. This is the element to which the {@link #annotation()} + * was applied. */ - public enum CancellationPolicy { - PROPAGATE, - IGNORE; + public abstract XTypeElement typeElement(); - private static CancellationPolicy from(XAnnotation annotation) { - checkArgument(XAnnotations.getClassName(annotation).equals(TypeNames.CANCELLATION_POLICY)); - return valueOf(getSimpleName(annotation.getAsEnum("fromSubcomponents"))); - } - } + /** + * The set of component dependencies listed in {@link Component#dependencies} or {@link + * dagger.producers.ProductionComponent#dependencies()}. + */ + public abstract ImmutableSet<ComponentRequirement> dependencies(); - /** Creates a {@link ComponentDescriptor}. */ - static ComponentDescriptor create( - ComponentAnnotation componentAnnotation, - XTypeElement component, - ImmutableSet<ComponentRequirement> componentDependencies, - ImmutableSet<ModuleDescriptor> transitiveModules, - ImmutableMap<XMethodElement, ComponentRequirement> dependenciesByDependencyMethod, - ImmutableSet<Scope> scopes, - ImmutableSet<ComponentDescriptor> subcomponentsFromModules, - ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> subcomponentsByFactoryMethod, - ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> subcomponentsByBuilderMethod, - ImmutableSet<ComponentMethodDescriptor> componentMethods, - Optional<ComponentCreatorDescriptor> creator) { - ComponentDescriptor descriptor = - new AutoValue_ComponentDescriptor( - componentAnnotation, - component, - componentDependencies, - transitiveModules, - dependenciesByDependencyMethod, - scopes, - subcomponentsFromModules, - subcomponentsByFactoryMethod, - subcomponentsByBuilderMethod, - componentMethods, - creator); - return descriptor; - } + /** + * The {@link ModuleDescriptor modules} declared in {@link Component#modules()} and reachable by + * traversing {@link Module#includes()}. + */ + public abstract ImmutableSet<ModuleDescriptor> modules(); - /** The annotation that specifies that {@link #typeElement()} is a component. */ - public abstract ComponentAnnotation annotation(); + /** The scopes of the component. */ + public abstract ImmutableSet<Scope> scopes(); + + /** + * All {@linkplain Subcomponent direct child} components that are declared by a {@linkplain + * Module#subcomponents() module's subcomponents}. + */ + abstract ImmutableSet<ComponentDescriptor> childComponentsDeclaredByModules(); + + /** + * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent + * factory method. + */ + public abstract ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> + childComponentsDeclaredByFactoryMethods(); + + /** + * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent + * builder method. + */ + abstract ImmutableMap<ComponentMethodDescriptor, ComponentDescriptor> + childComponentsDeclaredByBuilderEntryPoints(); + + public abstract ImmutableSet<ComponentMethodDescriptor> componentMethods(); + + /** Returns a descriptor for the creator type for this component type, if the user defined one. */ + public abstract Optional<ComponentCreatorDescriptor> creatorDescriptor(); /** Returns {@code true} if this is a subcomponent. */ public final boolean isSubcomponent() { @@ -136,18 +157,6 @@ public abstract class ComponentDescriptor { return annotation().isRealComponent(); } - /** - * The element that defines the component. This is the element to which the {@link #annotation()} - * was applied. - */ - public abstract XTypeElement typeElement(); - - /** - * The set of component dependencies listed in {@link Component#dependencies} or {@link - * dagger.producers.ProductionComponent#dependencies()}. - */ - public abstract ImmutableSet<ComponentRequirement> dependencies(); - /** The non-abstract {@link #modules()} and the {@link #dependencies()}. */ public final ImmutableSet<ComponentRequirement> dependenciesAndConcreteModules() { return Stream.concat( @@ -158,12 +167,6 @@ public abstract class ComponentDescriptor { .collect(toImmutableSet()); } - /** - * The {@link ModuleDescriptor modules} declared in {@link Component#modules()} and reachable by - * traversing {@link Module#includes()}. - */ - public abstract ImmutableSet<ModuleDescriptor> modules(); - /** The types of the {@link #modules()}. */ public final ImmutableSet<XTypeElement> moduleTypes() { return modules().stream().map(ModuleDescriptor::moduleElement).collect(toImmutableSet()); @@ -198,13 +201,21 @@ public abstract class ComponentDescriptor { } /** - * This component's {@linkplain #dependencies() dependencies} keyed by each provision or - * production method defined by that dependency. Note that the dependencies' types are not simply - * the enclosing type of the method; a method may be declared by a supertype of the actual - * dependency. + * Returns this component's dependencies keyed by its provision/production method. + * + * <p>Note that the dependencies' types are not simply the enclosing type of the method; a method + * may be declared by a supertype of the actual dependency. */ - public abstract ImmutableMap<XMethodElement, ComponentRequirement> - dependenciesByDependencyMethod(); + @Memoized + public ImmutableMap<XMethodElement, ComponentRequirement> dependenciesByDependencyMethod() { + ImmutableMap.Builder<XMethodElement, ComponentRequirement> builder = ImmutableMap.builder(); + for (ComponentRequirement componentDependency : dependencies()) { + XTypeElements.getAllMethods(componentDependency.typeElement()).stream() + .filter(ComponentDescriptor::isComponentContributionMethod) + .forEach(method -> builder.put(method, componentDependency)); + } + return builder.buildOrThrow(); + } /** The {@linkplain #dependencies() component dependency} that defines a method. */ public final ComponentRequirement getDependencyThatDefinesMethod(XElement method) { @@ -216,9 +227,6 @@ public abstract class ComponentDescriptor { return dependenciesByDependencyMethod().get(method); } - /** The scopes of the component. */ - public abstract ImmutableSet<Scope> scopes(); - /** * All {@link Subcomponent}s which are direct children of this component. This includes * subcomponents installed from {@link Module#subcomponents()} as well as subcomponent {@linkplain @@ -233,19 +241,6 @@ public abstract class ComponentDescriptor { .build(); } - /** - * All {@linkplain Subcomponent direct child} components that are declared by a {@linkplain - * Module#subcomponents() module's subcomponents}. - */ - abstract ImmutableSet<ComponentDescriptor> childComponentsDeclaredByModules(); - - /** - * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent - * factory method. - */ - public abstract ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> - childComponentsDeclaredByFactoryMethods(); - /** Returns a map of {@link #childComponents()} indexed by {@link #typeElement()}. */ @Memoized public ImmutableMap<XTypeElement, ComponentDescriptor> childComponentsByElement() { @@ -259,13 +254,6 @@ public abstract class ComponentDescriptor { childComponentsDeclaredByFactoryMethods().inverse().get(childComponent)); } - /** - * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent - * builder method. - */ - abstract ImmutableBiMap<ComponentMethodDescriptor, ComponentDescriptor> - childComponentsDeclaredByBuilderEntryPoints(); - private final Supplier<ImmutableMap<XTypeElement, ComponentDescriptor>> childComponentsByBuilderType = Suppliers.memoize( @@ -285,8 +273,6 @@ public abstract class ComponentDescriptor { builderType.getQualifiedName()); } - public abstract ImmutableSet<ComponentMethodDescriptor> componentMethods(); - /** Returns the first component method associated with this binding request, if one exists. */ public Optional<ComponentMethodDescriptor> firstMatchingComponentMethod(BindingRequest request) { return Optional.ofNullable(firstMatchingComponentMethods().get(request)); @@ -308,11 +294,6 @@ public abstract class ComponentDescriptor { .collect(toImmutableSet()); } - // TODO(gak): Consider making this non-optional and revising the - // interaction between the spec & generation - /** Returns a descriptor for the creator type for this component type, if the user defined one. */ - public abstract Optional<ComponentCreatorDescriptor> creatorDescriptor(); - /** * Returns {@code true} for components that have a creator, either because the user {@linkplain * #creatorDescriptor() specified one} or because it's a top-level component with an implicit @@ -408,4 +389,204 @@ public abstract class ComponentDescriptor { static boolean isComponentProductionMethod(XMethodElement method) { return isComponentContributionMethod(method) && isFutureType(method.getReturnType()); } + + /** A factory for creating a {@link ComponentDescriptor}. */ + @Singleton + public static final class Factory implements ClearableCache { + private final XProcessingEnv processingEnv; + private final DependencyRequestFactory dependencyRequestFactory; + private final ModuleDescriptor.Factory moduleDescriptorFactory; + private final InjectionAnnotations injectionAnnotations; + private final DaggerSuperficialValidation superficialValidation; + private final Map<XTypeElement, ComponentDescriptor> cache = new HashMap<>(); + + @Inject + Factory( + XProcessingEnv processingEnv, + DependencyRequestFactory dependencyRequestFactory, + ModuleDescriptor.Factory moduleDescriptorFactory, + InjectionAnnotations injectionAnnotations, + DaggerSuperficialValidation superficialValidation) { + this.processingEnv = processingEnv; + this.dependencyRequestFactory = dependencyRequestFactory; + this.moduleDescriptorFactory = moduleDescriptorFactory; + this.injectionAnnotations = injectionAnnotations; + this.superficialValidation = superficialValidation; + } + + /** Returns a descriptor for a root component type. */ + public ComponentDescriptor rootComponentDescriptor(XTypeElement typeElement) { + Optional<ComponentAnnotation> annotation = + rootComponentAnnotation(typeElement, superficialValidation); + checkArgument(annotation.isPresent(), "%s must have a component annotation", typeElement); + return create(typeElement, annotation.get()); + } + + /** Returns a descriptor for a subcomponent type. */ + public ComponentDescriptor subcomponentDescriptor(XTypeElement typeElement) { + Optional<ComponentAnnotation> annotation = + subcomponentAnnotation(typeElement, superficialValidation); + checkArgument(annotation.isPresent(), "%s must have a subcomponent annotation", typeElement); + return create(typeElement, annotation.get()); + } + + /** + * Returns a descriptor for a fictional component based on a module type in order to validate + * its bindings. + */ + public ComponentDescriptor moduleComponentDescriptor(XTypeElement typeElement) { + Optional<ModuleAnnotation> annotation = moduleAnnotation(typeElement, superficialValidation); + checkArgument(annotation.isPresent(), "%s must have a module annotation", typeElement); + return create(typeElement, ComponentAnnotation.fromModuleAnnotation(annotation.get())); + } + + private ComponentDescriptor create( + XTypeElement typeElement, ComponentAnnotation componentAnnotation) { + return reentrantComputeIfAbsent( + cache, typeElement, unused -> createUncached(typeElement, componentAnnotation)); + } + + private ComponentDescriptor createUncached( + XTypeElement typeElement, ComponentAnnotation componentAnnotation) { + ImmutableSet<ComponentRequirement> componentDependencies = + componentAnnotation.dependencyTypes().stream() + .map(ComponentRequirement::forDependency) + .collect(toImmutableSet()); + + // Start with the component's modules. For fictional components built from a module, start + // with that module. + ImmutableSet<XTypeElement> modules = + componentAnnotation.isRealComponent() + ? componentAnnotation.modules() + : ImmutableSet.of(typeElement); + + ImmutableSet<ModuleDescriptor> transitiveModules = + moduleDescriptorFactory.transitiveModules(modules); + + ImmutableSet.Builder<ComponentMethodDescriptor> componentMethodsBuilder = + ImmutableSet.builder(); + ImmutableBiMap.Builder<ComponentMethodDescriptor, ComponentDescriptor> + subcomponentsByFactoryMethod = ImmutableBiMap.builder(); + ImmutableMap.Builder<ComponentMethodDescriptor, ComponentDescriptor> + subcomponentsByBuilderMethod = ImmutableBiMap.builder(); + if (componentAnnotation.isRealComponent()) { + for (XMethodElement componentMethod : getAllUnimplementedMethods(typeElement)) { + ComponentMethodDescriptor componentMethodDescriptor = + getDescriptorForComponentMethod(componentAnnotation, typeElement, componentMethod); + componentMethodsBuilder.add(componentMethodDescriptor); + componentMethodDescriptor + .subcomponent() + .ifPresent( + subcomponent -> { + // If the dependency request is present, that means the method returns the + // subcomponent factory. + if (componentMethodDescriptor.dependencyRequest().isPresent()) { + subcomponentsByBuilderMethod.put(componentMethodDescriptor, subcomponent); + } else { + subcomponentsByFactoryMethod.put(componentMethodDescriptor, subcomponent); + } + }); + } + } + + // Validation should have ensured that this set will have at most one element. + ImmutableSet<XTypeElement> enclosedCreators = + enclosedAnnotatedTypes(typeElement, creatorAnnotationsFor(componentAnnotation)); + Optional<ComponentCreatorDescriptor> creatorDescriptor = + enclosedCreators.isEmpty() + ? Optional.empty() + : Optional.of( + ComponentCreatorDescriptor.create( + getOnlyElement(enclosedCreators), dependencyRequestFactory)); + + ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(typeElement); + if (componentAnnotation.isProduction()) { + scopes = + ImmutableSet.<Scope>builder() + .addAll(scopes).add(productionScope(processingEnv)) + .build(); + } + + ImmutableSet<ComponentDescriptor> subcomponentsFromModules = + transitiveModules.stream() + .flatMap(transitiveModule -> transitiveModule.subcomponentDeclarations().stream()) + .map(SubcomponentDeclaration::subcomponentType) + .map(this::subcomponentDescriptor) + .collect(toImmutableSet()); + + return new AutoValue_ComponentDescriptor( + componentAnnotation, + typeElement, + componentDependencies, + transitiveModules, + scopes, + subcomponentsFromModules, + subcomponentsByFactoryMethod.buildOrThrow(), + subcomponentsByBuilderMethod.buildOrThrow(), + componentMethodsBuilder.build(), + creatorDescriptor); + } + + private ComponentMethodDescriptor getDescriptorForComponentMethod( + ComponentAnnotation componentAnnotation, + XTypeElement componentElement, + XMethodElement componentMethod) { + ComponentMethodDescriptor.Builder descriptor = + ComponentMethodDescriptor.builder(componentMethod); + + XMethodType resolvedComponentMethod = componentMethod.asMemberOf(componentElement.getType()); + XType returnType = resolvedComponentMethod.getReturnType(); + if (isDeclared(returnType) + && !injectionAnnotations.getQualifier(componentMethod).isPresent()) { + XTypeElement returnTypeElement = returnType.getTypeElement(); + if (returnTypeElement.hasAnyAnnotation(subcomponentAnnotations())) { + // It's a subcomponent factory method. There is no dependency request, and there could be + // any number of parameters. Just return the descriptor. + return descriptor.subcomponent(subcomponentDescriptor(returnTypeElement)).build(); + } + if (isSubcomponentCreator(returnTypeElement)) { + descriptor.subcomponent( + subcomponentDescriptor(returnTypeElement.getEnclosingTypeElement())); + } + } + + switch (componentMethod.getParameters().size()) { + case 0: + checkArgument( + !isVoid(returnType), "component method cannot be void: %s", componentMethod); + descriptor.dependencyRequest( + componentAnnotation.isProduction() + ? dependencyRequestFactory.forComponentProductionMethod( + componentMethod, resolvedComponentMethod) + : dependencyRequestFactory.forComponentProvisionMethod( + componentMethod, resolvedComponentMethod)); + break; + + case 1: + checkArgument( + isVoid(returnType) + // TODO(bcorso): Replace this with isSameType()? + || returnType + .getTypeName() + .equals(resolvedComponentMethod.getParameterTypes().get(0).getTypeName()), + "members injection method must return void or parameter type: %s", + componentMethod); + descriptor.dependencyRequest( + dependencyRequestFactory.forComponentMembersInjectionMethod( + componentMethod, resolvedComponentMethod)); + break; + + default: + throw new IllegalArgumentException( + "component method has too many parameters: " + componentMethod); + } + + return descriptor.build(); + } + + @Override + public void clearCache() { + cache.clear(); + } + } } diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java b/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java deleted file mode 100644 index 1d0945078..000000000 --- a/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2014 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.binding; - -import static androidx.room.compiler.processing.XTypeKt.isVoid; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotation; -import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation; -import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations; -import static dagger.internal.codegen.base.ComponentCreatorAnnotation.creatorAnnotationsFor; -import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; -import static dagger.internal.codegen.base.Scopes.productionScope; -import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes; -import static dagger.internal.codegen.binding.ConfigurationAnnotations.isSubcomponentCreator; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods; -import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; - -import androidx.room.compiler.processing.XMethodElement; -import androidx.room.compiler.processing.XMethodType; -import androidx.room.compiler.processing.XProcessingEnv; -import androidx.room.compiler.processing.XType; -import androidx.room.compiler.processing.XTypeElement; -import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import dagger.internal.codegen.base.ComponentAnnotation; -import dagger.internal.codegen.base.DaggerSuperficialValidation; -import dagger.internal.codegen.base.ModuleAnnotation; -import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; -import dagger.internal.codegen.model.Scope; -import dagger.internal.codegen.xprocessing.XTypeElements; -import java.util.Optional; -import javax.inject.Inject; - -/** A factory for {@link ComponentDescriptor}s. */ -public final class ComponentDescriptorFactory { - private final XProcessingEnv processingEnv; - private final DependencyRequestFactory dependencyRequestFactory; - private final ModuleDescriptor.Factory moduleDescriptorFactory; - private final InjectionAnnotations injectionAnnotations; - private final DaggerSuperficialValidation superficialValidation; - - @Inject - ComponentDescriptorFactory( - XProcessingEnv processingEnv, - DependencyRequestFactory dependencyRequestFactory, - ModuleDescriptor.Factory moduleDescriptorFactory, - InjectionAnnotations injectionAnnotations, - DaggerSuperficialValidation superficialValidation) { - this.processingEnv = processingEnv; - this.dependencyRequestFactory = dependencyRequestFactory; - this.moduleDescriptorFactory = moduleDescriptorFactory; - this.injectionAnnotations = injectionAnnotations; - this.superficialValidation = superficialValidation; - } - - /** Returns a descriptor for a root component type. */ - public ComponentDescriptor rootComponentDescriptor(XTypeElement typeElement) { - Optional<ComponentAnnotation> annotation = - rootComponentAnnotation(typeElement, superficialValidation); - checkArgument(annotation.isPresent(), "%s must have a component annotation", typeElement); - return create(typeElement, annotation.get()); - } - - /** Returns a descriptor for a subcomponent type. */ - public ComponentDescriptor subcomponentDescriptor(XTypeElement typeElement) { - Optional<ComponentAnnotation> annotation = - subcomponentAnnotation(typeElement, superficialValidation); - checkArgument(annotation.isPresent(), "%s must have a subcomponent annotation", typeElement); - return create(typeElement, annotation.get()); - } - - /** - * Returns a descriptor for a fictional component based on a module type in order to validate its - * bindings. - */ - public ComponentDescriptor moduleComponentDescriptor(XTypeElement typeElement) { - Optional<ModuleAnnotation> annotation = moduleAnnotation(typeElement, superficialValidation); - checkArgument(annotation.isPresent(), "%s must have a module annotation", typeElement); - return create(typeElement, ComponentAnnotation.fromModuleAnnotation(annotation.get())); - } - - private ComponentDescriptor create( - XTypeElement typeElement, ComponentAnnotation componentAnnotation) { - ImmutableSet<ComponentRequirement> componentDependencies = - componentAnnotation.dependencyTypes().stream() - .map(ComponentRequirement::forDependency) - .collect(toImmutableSet()); - - ImmutableMap.Builder<XMethodElement, ComponentRequirement> dependenciesByDependencyMethod = - ImmutableMap.builder(); - for (ComponentRequirement componentDependency : componentDependencies) { - XTypeElements.getAllMethods(componentDependency.typeElement()).stream() - .filter(ComponentDescriptor::isComponentContributionMethod) - .forEach(method -> dependenciesByDependencyMethod.put(method, componentDependency)); - } - - // Start with the component's modules. For fictional components built from a module, start with - // that module. - ImmutableSet<XTypeElement> modules = - componentAnnotation.isRealComponent() - ? componentAnnotation.modules() - : ImmutableSet.of(typeElement); - - ImmutableSet<ModuleDescriptor> transitiveModules = - moduleDescriptorFactory.transitiveModules(modules); - - ImmutableSet<ComponentDescriptor> subcomponentsFromModules = - transitiveModules.stream() - .flatMap(transitiveModule -> transitiveModule.subcomponentDeclarations().stream()) - .map(SubcomponentDeclaration::subcomponentType) - .map(this::subcomponentDescriptor) - .collect(toImmutableSet()); - - ImmutableSet.Builder<ComponentMethodDescriptor> componentMethodsBuilder = - ImmutableSet.builder(); - ImmutableBiMap.Builder<ComponentMethodDescriptor, ComponentDescriptor> - subcomponentsByFactoryMethod = ImmutableBiMap.builder(); - ImmutableBiMap.Builder<ComponentMethodDescriptor, ComponentDescriptor> - subcomponentsByBuilderMethod = ImmutableBiMap.builder(); - if (componentAnnotation.isRealComponent()) { - for (XMethodElement componentMethod : getAllUnimplementedMethods(typeElement)) { - ComponentMethodDescriptor componentMethodDescriptor = - getDescriptorForComponentMethod(componentAnnotation, typeElement, componentMethod); - componentMethodsBuilder.add(componentMethodDescriptor); - componentMethodDescriptor - .subcomponent() - .ifPresent( - subcomponent -> { - // If the dependency request is present, that means the method returns the - // subcomponent factory. - if (componentMethodDescriptor.dependencyRequest().isPresent()) { - subcomponentsByBuilderMethod.put(componentMethodDescriptor, subcomponent); - } else { - subcomponentsByFactoryMethod.put(componentMethodDescriptor, subcomponent); - } - }); - } - } - - // Validation should have ensured that this set will have at most one element. - ImmutableSet<XTypeElement> enclosedCreators = - enclosedAnnotatedTypes(typeElement, creatorAnnotationsFor(componentAnnotation)); - Optional<ComponentCreatorDescriptor> creatorDescriptor = - enclosedCreators.isEmpty() - ? Optional.empty() - : Optional.of( - ComponentCreatorDescriptor.create( - getOnlyElement(enclosedCreators), dependencyRequestFactory)); - - ImmutableSet<Scope> scopes = injectionAnnotations.getScopes(typeElement); - if (componentAnnotation.isProduction()) { - scopes = - ImmutableSet.<Scope>builder().addAll(scopes).add(productionScope(processingEnv)).build(); - } - - return ComponentDescriptor.create( - componentAnnotation, - typeElement, - componentDependencies, - transitiveModules, - dependenciesByDependencyMethod.build(), - scopes, - subcomponentsFromModules, - subcomponentsByFactoryMethod.build(), - subcomponentsByBuilderMethod.build(), - componentMethodsBuilder.build(), - creatorDescriptor); - } - - private ComponentMethodDescriptor getDescriptorForComponentMethod( - ComponentAnnotation componentAnnotation, - XTypeElement componentElement, - XMethodElement componentMethod) { - ComponentMethodDescriptor.Builder descriptor = - ComponentMethodDescriptor.builder(componentMethod); - - XMethodType resolvedComponentMethod = componentMethod.asMemberOf(componentElement.getType()); - XType returnType = resolvedComponentMethod.getReturnType(); - if (isDeclared(returnType) && !injectionAnnotations.getQualifier(componentMethod).isPresent()) { - XTypeElement returnTypeElement = returnType.getTypeElement(); - if (returnTypeElement.hasAnyAnnotation(subcomponentAnnotations())) { - // It's a subcomponent factory method. There is no dependency request, and there could be - // any number of parameters. Just return the descriptor. - return descriptor.subcomponent(subcomponentDescriptor(returnTypeElement)).build(); - } - if (isSubcomponentCreator(returnTypeElement)) { - descriptor.subcomponent( - subcomponentDescriptor(returnTypeElement.getEnclosingTypeElement())); - } - } - - switch (componentMethod.getParameters().size()) { - case 0: - checkArgument(!isVoid(returnType), "component method cannot be void: %s", componentMethod); - descriptor.dependencyRequest( - componentAnnotation.isProduction() - ? dependencyRequestFactory.forComponentProductionMethod( - componentMethod, resolvedComponentMethod) - : dependencyRequestFactory.forComponentProvisionMethod( - componentMethod, resolvedComponentMethod)); - break; - - case 1: - checkArgument( - isVoid(returnType) - // TODO(bcorso): Replace this with isSameType()? - || returnType - .getTypeName() - .equals(resolvedComponentMethod.getParameterTypes().get(0).getTypeName()), - "members injection method must return void or parameter type: %s", - componentMethod); - descriptor.dependencyRequest( - dependencyRequestFactory.forComponentMembersInjectionMethod( - componentMethod, resolvedComponentMethod)); - break; - - default: - throw new IllegalArgumentException( - "component method has too many parameters: " + componentMethod); - } - - return descriptor.build(); - } -} diff --git a/java/dagger/internal/codegen/binding/ComponentRequirement.java b/java/dagger/internal/codegen/binding/ComponentRequirement.java index df105773e..1779ec1d6 100644 --- a/java/dagger/internal/codegen/binding/ComponentRequirement.java +++ b/java/dagger/internal/codegen/binding/ComponentRequirement.java @@ -145,7 +145,7 @@ public abstract class ComponentRequirement { * <p>Alternatively, if the module is a Kotlin Object then the binding methods are considered * {@code static}, requiring no module instance. */ - private boolean requiresModuleInstance() { + public boolean requiresModuleInstance() { if (typeElement().isKotlinObject() || typeElement().isCompanionObject()) { return false; } diff --git a/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java b/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java index 80591b121..853ee994b 100644 --- a/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java +++ b/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java @@ -25,12 +25,10 @@ import static dagger.internal.codegen.base.RequestKinds.requestType; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XProcessingEnv; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import dagger.Provides; import dagger.internal.codegen.base.Formatter; import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.xprocessing.XTypes; -import dagger.producers.Produces; import java.util.Optional; import javax.inject.Inject; @@ -43,8 +41,7 @@ import javax.inject.Inject; * <dd>{@code @Qualifier SomeType is provided at\n ComponentType.method()} * <dt>For component injection methods * <dd>{@code SomeType is injected at\n ComponentType.method(foo)} - * <dt>For parameters to {@link Provides @Provides}, {@link Produces @Produces}, or {@link - * Inject @Inject} methods: + * <dt>For parameters to {@code @Provides}, {@code @Produces}, or {@code @Inject} methods: * <dd>{@code @Qualified ResolvedType is injected at\n EnclosingType.method([…, ]param[, …])} * <dt>For parameters to {@link Inject @Inject} constructors: * <dd>{@code @Qualified ResolvedType is injected at\n EnclosingType([…, ]param[, …])} diff --git a/java/dagger/internal/codegen/binding/FrameworkField.java b/java/dagger/internal/codegen/binding/FrameworkField.java index b1d1cf91b..a9f3bbfbd 100644 --- a/java/dagger/internal/codegen/binding/FrameworkField.java +++ b/java/dagger/internal/codegen/binding/FrameworkField.java @@ -24,12 +24,14 @@ import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTOR; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import androidx.room.compiler.processing.XElement; -import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; import com.google.common.base.CaseFormat; +import com.google.common.base.Preconditions; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; +import dagger.internal.codegen.base.MapType; +import dagger.internal.codegen.javapoet.TypeNames; import java.util.Optional; /** @@ -49,16 +51,17 @@ public abstract class FrameworkField { /** * Creates a framework field. * - * @param frameworkClassName the name of the framework class (e.g., {@link javax.inject.Provider}) - * @param valueTypeName the name of the type parameter of the framework class (e.g., {@code Foo} - * for {@code Provider<Foo>} - * @param fieldName the name of the field + * @param fieldType the type of the framework field (e.g., {@code Provider<Foo>}). + * @param fieldName the base name of the field. The name of the raw type of the field will be + * added as a suffix */ - public static FrameworkField create( - ClassName frameworkClassName, TypeName valueTypeName, String fieldName) { - String suffix = frameworkClassName.simpleName(); + public static FrameworkField create(TypeName fieldType, String fieldName) { + Preconditions.checkState( + fieldType instanceof ClassName || fieldType instanceof ParameterizedTypeName, + "Can only create a field with a class name or parameterized type name"); + String suffix = ((ClassName) TypeNames.rawTypeName(fieldType)).simpleName(); return new AutoValue_FrameworkField( - ParameterizedTypeName.get(frameworkClassName, valueTypeName), + fieldType, fieldName.endsWith(suffix) ? fieldName : fieldName + suffix); } @@ -71,15 +74,26 @@ public abstract class FrameworkField { public static FrameworkField forBinding( ContributionBinding binding, Optional<ClassName> frameworkClassName) { return create( - frameworkClassName.orElse(binding.frameworkType().frameworkClassName()), - fieldValueType(binding).getTypeName(), + fieldType(binding, frameworkClassName.orElse(binding.frameworkType().frameworkClassName())), frameworkFieldName(binding)); } - private static XType fieldValueType(ContributionBinding binding) { - return binding.contributionType().isMultibinding() - ? binding.contributedType() - : binding.key().type().xprocessing(); + private static TypeName fieldType(ContributionBinding binding, ClassName frameworkClassName) { + if (binding.contributionType().isMultibinding()) { + return ParameterizedTypeName.get(frameworkClassName, binding.contributedType().getTypeName()); + } + + // If the binding key type is a Map<K, Provider<V>>, we need to change field type to a raw + // type. This is because it actually needs to be changed to Map<K, dagger.internal.Provider<V>>, + // but that gets into assignment issues when the field is passed to methods that expect + // Map<K, javax.inject.Provider<V>>. We could add casts everywhere, but it is easier to just + // make the field itself a raw type. + if (MapType.isMapOfProvider(binding.contributedType())) { + return frameworkClassName; + } + + return ParameterizedTypeName.get( + frameworkClassName, binding.key().type().xprocessing().getTypeName()); } private static String frameworkFieldName(ContributionBinding binding) { @@ -104,7 +118,7 @@ public abstract class FrameworkField { } } - public abstract ParameterizedTypeName type(); + public abstract TypeName type(); public abstract String name(); } diff --git a/java/dagger/internal/codegen/binding/FrameworkType.java b/java/dagger/internal/codegen/binding/FrameworkType.java index ce3b149bc..c99594892 100644 --- a/java/dagger/internal/codegen/binding/FrameworkType.java +++ b/java/dagger/internal/codegen/binding/FrameworkType.java @@ -96,7 +96,8 @@ public enum FrameworkType { case PROVIDER_OF_LAZY: return Expression.create( - from.type().rewrapType(TypeNames.LAZY).wrapType(TypeNames.PROVIDER), codeBlock); + from.type().rewrapType(TypeNames.LAZY).wrapType(TypeNames.DAGGER_PROVIDER), + codeBlock); case FUTURE: return Expression.create(from.type().rewrapType(TypeNames.LISTENABLE_FUTURE), codeBlock); @@ -178,7 +179,7 @@ public enum FrameworkType { public ClassName frameworkClassName() { switch (this) { case PROVIDER: - return TypeNames.PROVIDER; + return TypeNames.DAGGER_PROVIDER; case PRODUCER_NODE: // TODO(cgdecker): Replace this with new class for representing internal producer nodes. // Currently the new class is CancellableProducer, but it may be changed to ProducerNode and diff --git a/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java b/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java index 0e17d9687..bd439bf93 100644 --- a/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java +++ b/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java @@ -19,12 +19,10 @@ package dagger.internal.codegen.binding; import static dagger.internal.codegen.binding.BindingType.PRODUCTION; import dagger.internal.codegen.model.RequestKind; -import dagger.producers.Producer; -import javax.inject.Provider; /** * A mapper for associating a {@link RequestKind} to a {@link FrameworkType}, dependent on the type - * of code to be generated (e.g., for {@link Provider} or {@link Producer}). + * of code to be generated (e.g., for {@code Provider} or {@code Producer}). */ public enum FrameworkTypeMapper { FOR_PROVIDER() { diff --git a/java/dagger/internal/codegen/binding/InjectionAnnotations.java b/java/dagger/internal/codegen/binding/InjectionAnnotations.java index a8adb15dd..d4e1f90b7 100644 --- a/java/dagger/internal/codegen/binding/InjectionAnnotations.java +++ b/java/dagger/internal/codegen/binding/InjectionAnnotations.java @@ -22,12 +22,13 @@ import static androidx.room.compiler.processing.XElementKt.isMethod; import static androidx.room.compiler.processing.XElementKt.isMethodParameter; import static androidx.room.compiler.processing.XElementKt.isTypeElement; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.binding.SourceFiles.factoryNameForElement; import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; -import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.xprocessing.XElements.asField; import static dagger.internal.codegen.xprocessing.XElements.asMethod; @@ -42,14 +43,17 @@ import androidx.room.compiler.processing.XExecutableElement; import androidx.room.compiler.processing.XFieldElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.DaggerSuperficialValidation; +import dagger.internal.codegen.base.ElementFormatter; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.Scope; +import dagger.internal.codegen.xprocessing.XAnnotations; import java.util.Optional; import java.util.stream.Stream; import javax.inject.Inject; @@ -100,18 +104,7 @@ public final class InjectionAnnotations { public ImmutableSet<Scope> getScopes(XElement element) { superficialValidation.validateTypeOf(element); ImmutableSet<Scope> scopes = - getScopesFromScopeMetadata(element) - .orElseGet( - () -> { - // Validate the annotation types before we check for @Scope, otherwise the @Scope - // annotation may appear to be missing (b/213880825). - superficialValidation.validateAnnotationTypesOf(element); - return element.getAllAnnotations().stream() - .filter(InjectionAnnotations::hasScopeAnnotation) - .map(DaggerAnnotation::from) - .map(Scope::scope) - .collect(toImmutableSet()); - }); + getScopesWithMetadata(element).orElseGet(() -> getScopesWithoutMetadata(element)); // Fully validate each scope to ensure its values are also valid. scopes.stream() @@ -120,7 +113,18 @@ public final class InjectionAnnotations { return scopes; } - private Optional<ImmutableSet<Scope>> getScopesFromScopeMetadata(XElement element) { + private ImmutableSet<Scope> getScopesWithoutMetadata(XElement element) { + // Validate the annotation types before we check for @Scope, otherwise the @Scope + // annotation may appear to be missing (b/213880825). + superficialValidation.validateAnnotationTypesOf(element); + return element.getAllAnnotations().stream() + .filter(InjectionAnnotations::hasScopeAnnotation) + .map(DaggerAnnotation::from) + .map(Scope::scope) + .collect(toImmutableSet()); + } + + private Optional<ImmutableSet<Scope>> getScopesWithMetadata(XElement element) { Optional<XAnnotation> scopeMetadata = getScopeMetadata(element); if (!scopeMetadata.isPresent()) { return Optional.empty(); @@ -129,13 +133,20 @@ public final class InjectionAnnotations { if (scopeName.isEmpty()) { return Optional.of(ImmutableSet.of()); } - XAnnotation scopeAnnotation = + ImmutableList<XAnnotation> scopeAnnotations = element.getAllAnnotations().stream() .filter( annotation -> scopeName.contentEquals( annotation.getType().getTypeElement().getQualifiedName())) - .collect(onlyElement()); + .collect(toImmutableList()); + checkState( + scopeAnnotations.size() == 1, + "Expected %s to have a scope annotation for %s but found: %s", + ElementFormatter.elementToString(element), + scopeName, + scopeAnnotations.stream().map(XAnnotations::toStableString).collect(toImmutableList())); + XAnnotation scopeAnnotation = getOnlyElement(scopeAnnotations); // Do superficial validation before we convert to a Scope, otherwise the @Scope annotation may // appear to be missing from the annotation if it's no longer on the classpath. superficialValidation.validateAnnotationTypeOf(element, scopeAnnotation); @@ -170,7 +181,7 @@ public final class InjectionAnnotations { return Optional.empty(); } - /* + /** * Returns the qualifier on the given element if it exists. * * <p>The {@code QualifierMetadata} is used to avoid superficial validation on unnecessary @@ -203,16 +214,8 @@ public final class InjectionAnnotations { public ImmutableSet<XAnnotation> getQualifiers(XElement element) { superficialValidation.validateTypeOf(element); ImmutableSet<XAnnotation> qualifiers = - getQualifiersFromQualifierMetadata(element) - .orElseGet( - () -> { - // Validate the annotation types before we check for @Qualifier, otherwise the - // @Qualifier annotation may appear to be missing (b/213880825). - superficialValidation.validateAnnotationTypesOf(element); - return element.getAllAnnotations().stream() - .filter(InjectionAnnotations::hasQualifierAnnotation) - .collect(toImmutableSet()); - }); + getQualifiersWithMetadata(element) + .orElseGet(() -> getQualifiersWithoutMetadata(element)); if (isField(element)) { XFieldElement field = asField(element); @@ -237,7 +240,16 @@ public final class InjectionAnnotations { return qualifiers; } - private Optional<ImmutableSet<XAnnotation>> getQualifiersFromQualifierMetadata(XElement element) { + private ImmutableSet<XAnnotation> getQualifiersWithoutMetadata(XElement element) { + // Validate the annotation types before we check for @Qualifier, otherwise the + // @Qualifier annotation may appear to be missing (b/213880825). + superficialValidation.validateAnnotationTypesOf(element); + return element.getAllAnnotations().stream() + .filter(InjectionAnnotations::hasQualifierAnnotation) + .collect(toImmutableSet()); + } + + private Optional<ImmutableSet<XAnnotation>> getQualifiersWithMetadata(XElement element) { Optional<XAnnotation> qualifierMetadata = getQualifierMetadata(element); if (!qualifierMetadata.isPresent()) { return Optional.empty(); diff --git a/java/dagger/internal/codegen/binding/InjectionSiteFactory.java b/java/dagger/internal/codegen/binding/InjectionSiteFactory.java index 5ede20042..36e831bfd 100644 --- a/java/dagger/internal/codegen/binding/InjectionSiteFactory.java +++ b/java/dagger/internal/codegen/binding/InjectionSiteFactory.java @@ -68,10 +68,13 @@ final class InjectionSiteFactory { XTypeElement typeElement = currentType.get().getTypeElement(); enclosingTypeElementOrder.put(typeElement, enclosingTypeElementOrder.size()); for (XElement enclosedElement : typeElement.getEnclosedElements()) { - enclosedElementOrder.put(enclosedElement, enclosedElementOrder.size()); injectionSiteVisitor .visit(enclosedElement, currentType.get()) - .ifPresent(injectionSites::add); + .ifPresent( + injectionSite -> { + enclosedElementOrder.put(enclosedElement, enclosedElementOrder.size()); + injectionSites.add(injectionSite); + }); } } return ImmutableSortedSet.copyOf( diff --git a/java/dagger/internal/codegen/binding/LegacyBindingGraph.java b/java/dagger/internal/codegen/binding/LegacyBindingGraph.java deleted file mode 100644 index b38697e8d..000000000 --- a/java/dagger/internal/codegen/binding/LegacyBindingGraph.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2014 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.binding; - -import androidx.room.compiler.processing.XTypeElement; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimaps; -import dagger.internal.codegen.model.Key; -import dagger.internal.codegen.model.RequestKind; -import java.util.Collection; -import java.util.Map; - -// TODO(bcorso): Remove the LegacyBindingGraph after we've migrated to the new BindingGraph. -/** The canonical representation of a full-resolved graph. */ -final class LegacyBindingGraph { - private final ComponentDescriptor componentDescriptor; - private final ImmutableMap<Key, ResolvedBindings> contributionBindings; - private final ImmutableMap<Key, ResolvedBindings> membersInjectionBindings; - private final ImmutableList<LegacyBindingGraph> subgraphs; - - LegacyBindingGraph( - ComponentDescriptor componentDescriptor, - ImmutableMap<Key, ResolvedBindings> contributionBindings, - ImmutableMap<Key, ResolvedBindings> membersInjectionBindings, - ImmutableList<LegacyBindingGraph> subgraphs) { - this.componentDescriptor = componentDescriptor; - this.contributionBindings = contributionBindings; - this.membersInjectionBindings = membersInjectionBindings; - this.subgraphs = checkForDuplicates(subgraphs); - } - - ComponentDescriptor componentDescriptor() { - return componentDescriptor; - } - - ResolvedBindings resolvedBindings(BindingRequest request) { - return request.isRequestKind(RequestKind.MEMBERS_INJECTION) - ? membersInjectionBindings.get(request.key()) - : contributionBindings.get(request.key()); - } - - Iterable<ResolvedBindings> resolvedBindings() { - // Don't return an immutable collection - this is only ever used for looping over all bindings - // in the graph. Copying is wasteful, especially if is a hashing collection, since the values - // should all, by definition, be distinct. - return Iterables.concat(membersInjectionBindings.values(), contributionBindings.values()); - } - - ImmutableList<LegacyBindingGraph> subgraphs() { - return subgraphs; - } - - private static ImmutableList<LegacyBindingGraph> checkForDuplicates( - ImmutableList<LegacyBindingGraph> graphs) { - Map<XTypeElement, Collection<LegacyBindingGraph>> duplicateGraphs = - Maps.filterValues( - Multimaps.index(graphs, graph -> graph.componentDescriptor().typeElement()).asMap(), - overlapping -> overlapping.size() > 1); - if (!duplicateGraphs.isEmpty()) { - throw new IllegalArgumentException("Expected no duplicates: " + duplicateGraphs); - } - return graphs; - } -} diff --git a/java/dagger/internal/codegen/binding/MapKeys.java b/java/dagger/internal/codegen/binding/MapKeys.java index c156dbb06..61ef9f2b2 100644 --- a/java/dagger/internal/codegen/binding/MapKeys.java +++ b/java/dagger/internal/codegen/binding/MapKeys.java @@ -199,5 +199,26 @@ public final class MapKeys { .build()); } + /** + * Returns if this binding is a map binding and uses @LazyClassKey for contributing class keys. + * + * <p>@LazyClassKey won't co-exist with @ClassKey in the graph, since the same binding type cannot + * use more than one @MapKey annotation type and Dagger validation will fail. + */ + public static boolean useLazyClassKey(Binding binding, BindingGraph graph) { + if (!binding.dependencies().isEmpty()) { + ContributionBinding contributionBinding = + graph.contributionBinding(binding.dependencies().iterator().next().key()); + return contributionBinding.mapKey().isPresent() + && contributionBinding + .mapKey() + .get() + .xprocessing() + .getClassName() + .equals(TypeNames.LAZY_CLASS_KEY); + } + return false; + } + private MapKeys() {} } diff --git a/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java b/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java index e98abcca2..d7fea80b7 100644 --- a/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java +++ b/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java @@ -16,13 +16,14 @@ package dagger.internal.codegen.binding; +import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; -import static java.util.stream.Collectors.joining; import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XExecutableElement; @@ -33,12 +34,13 @@ import androidx.room.compiler.processing.XMethodType; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.XVariableElement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Streams; import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.Formatter; import dagger.internal.codegen.xprocessing.XAnnotations; import dagger.internal.codegen.xprocessing.XTypes; import java.util.Iterator; -import java.util.List; import java.util.Optional; import javax.inject.Inject; @@ -46,6 +48,8 @@ import javax.inject.Inject; public final class MethodSignatureFormatter extends Formatter<XExecutableElement> { private static final ClassName JET_BRAINS_NOT_NULL = ClassName.get("org.jetbrains.annotations", "NotNull"); + private static final ClassName JET_BRAINS_NULLABLE = + ClassName.get("org.jetbrains.annotations", "Nullable"); private final InjectionAnnotations injectionAnnotations; @@ -106,15 +110,9 @@ public final class MethodSignatureFormatter extends Formatter<XExecutableElement XTypeElement container, boolean includeReturnType) { StringBuilder builder = new StringBuilder(); - List<XAnnotation> annotations = method.getAllAnnotations(); - if (!annotations.isEmpty()) { - builder.append( - annotations.stream() - // Filter out @NotNull annotations added by KAPT to make error messages consistent - .filter(annotation -> !annotation.getClassName().equals(JET_BRAINS_NOT_NULL)) - .map(MethodSignatureFormatter::formatAnnotation) - .collect(joining(" "))) - .append(" "); + ImmutableList<String> formattedAnnotations = formatedAnnotations(method); + if (!formattedAnnotations.isEmpty()) { + builder.append(String.join(" ", formattedAnnotations)).append(" "); } if (getSimpleName(method).contentEquals("<init>")) { builder.append(container.getQualifiedName()); @@ -154,6 +152,34 @@ public final class MethodSignatureFormatter extends Formatter<XExecutableElement return stripCommonTypePrefixes(XTypes.toStableString(type)); } + private static ImmutableList<String> formatedAnnotations(XExecutableElement executableElement) { + Nullability nullability = Nullability.of(executableElement); + ImmutableList<String> formattedAnnotations = + Streams.concat( + executableElement.getAllAnnotations().stream() + // Filter out @NotNull annotations added by KAPT to make error messages + // consistent + .filter(annotation -> !annotation.getClassName().equals(JET_BRAINS_NOT_NULL)) + .map(MethodSignatureFormatter::formatAnnotation), + nullability.nullableAnnotations().stream() + // Filter out @NotNull annotations added by KAPT to make error messages + // consistent + .filter(annotation -> !annotation.equals(JET_BRAINS_NOT_NULL)) + .map(annotation -> String.format("@%s", annotation.canonicalName()))) + .distinct() + .collect(toImmutableList()); + if (nullability.isKotlinTypeNullable() + && nullability.nullableAnnotations().stream().noneMatch(JET_BRAINS_NULLABLE::equals) + && getProcessingEnv(executableElement).findTypeElement(JET_BRAINS_NULLABLE) != null) { + formattedAnnotations = + ImmutableList.<String>builder() + .addAll(formattedAnnotations) + .add(String.format("@%s", JET_BRAINS_NULLABLE.canonicalName())) + .build(); + } + return formattedAnnotations; + } + private static String formatAnnotation(XAnnotation annotation) { return stripCommonTypePrefixes(XAnnotations.toString(annotation)); } diff --git a/java/dagger/internal/codegen/binding/MonitoringModules.java b/java/dagger/internal/codegen/binding/MonitoringModules.java new file mode 100644 index 000000000..fc6dc021c --- /dev/null +++ b/java/dagger/internal/codegen/binding/MonitoringModules.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.binding; + +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.base.ClearableCache; +import java.util.HashSet; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Keeps track of modules generated in the current round by {@link MonitoringModuleGenerator}. */ +@Singleton +public final class MonitoringModules implements ClearableCache { + Set<ClassName> cache = new HashSet<>(); + + @Inject + MonitoringModules() {} + + public void add(ClassName module) { + cache.add(module); + } + + public boolean isEmpty() { + return cache.isEmpty(); + } + + @Override + public void clearCache() { + cache.clear(); + } +} diff --git a/java/dagger/internal/codegen/binding/Nullability.java b/java/dagger/internal/codegen/binding/Nullability.java index 190227ba0..4231a8fb0 100644 --- a/java/dagger/internal/codegen/binding/Nullability.java +++ b/java/dagger/internal/codegen/binding/Nullability.java @@ -18,7 +18,7 @@ package dagger.internal.codegen.binding; import static androidx.room.compiler.processing.XElementKt.isMethod; import static androidx.room.compiler.processing.XElementKt.isVariableElement; -import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.asVariable; @@ -27,7 +27,10 @@ import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XNullability; import androidx.room.compiler.processing.XType; import com.google.auto.value.AutoValue; -import java.util.Optional; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.xprocessing.XAnnotations; +import java.util.stream.Stream; /** * Contains information about the nullability of an element. @@ -42,16 +45,43 @@ import java.util.Optional; @AutoValue public abstract class Nullability { /** A constant that can represent any non-null element. */ - public static final Nullability NOT_NULLABLE = new AutoValue_Nullability(false, Optional.empty()); + public static final Nullability NOT_NULLABLE = + new AutoValue_Nullability(false, ImmutableSet.of()); public static Nullability of(XElement element) { - Optional<XAnnotation> nullableAnnotation = getNullableAnnotation(element); - boolean isNullable = isKotlinTypeNullable(element) || nullableAnnotation.isPresent(); - return isNullable ? new AutoValue_Nullability(isNullable, nullableAnnotation) : NOT_NULLABLE; + return new AutoValue_Nullability( + /* isKotlinTypeNullable= */ isKotlinTypeNullable(element), + /* nullableAnnotations= */ getNullableAnnotations(element)); } + private static ImmutableSet<ClassName> getNullableAnnotations(XElement element) { + return getNullableAnnotations(element.getAllAnnotations().stream(), ImmutableSet.of()); + } + + private static ImmutableSet<ClassName> getNullableAnnotations( + Stream<XAnnotation> annotations, + ImmutableSet<ClassName> filterSet) { + return annotations + .map(XAnnotations::getClassName) + .filter(annotation -> annotation.simpleName().contentEquals("Nullable")) + .filter(annotation -> !filterSet.contains(annotation)) + .collect(toImmutableSet()); + } + + /** + * Returns {@code true} if the element's type is a Kotlin nullable type, e.g. {@code Foo?}. + * + * <p>Note that this method ignores any {@code @Nullable} type annotations and only looks for + * explicit {@code ?} usages on kotlin types. + */ private static boolean isKotlinTypeNullable(XElement element) { - if (isMethod(element)) { + if (element.getClosestMemberContainer().isFromJava()) { + // Note: Technically, it isn't possible for Java sources to have nullable types like in Kotlin + // sources, but for some reason KSP treats certain types as nullable if they have a + // specific @Nullable (TYPE_USE target) annotation. Thus, to avoid inconsistencies with KAPT, + // just return false if this element is from a java source. + return false; + } else if (isMethod(element)) { return isKotlinTypeNullable(asMethod(element).getReturnType()); } else if (isVariableElement(element)) { return isKotlinTypeNullable(asVariable(element).getType()); @@ -64,16 +94,13 @@ public abstract class Nullability { return type.getNullability() == XNullability.NULLABLE; } - /** Returns the first type that specifies this' nullability, or empty if none. */ - private static Optional<XAnnotation> getNullableAnnotation(XElement element) { - return element.getAllAnnotations().stream() - .filter(annotation -> getClassName(annotation).simpleName().contentEquals("Nullable")) - .findFirst(); - } + public abstract boolean isKotlinTypeNullable(); - public abstract boolean isNullable(); + public abstract ImmutableSet<ClassName> nullableAnnotations(); - public abstract Optional<XAnnotation> nullableAnnotation(); + public final boolean isNullable() { + return isKotlinTypeNullable() || !nullableAnnotations().isEmpty(); + } Nullability() {} } diff --git a/java/dagger/internal/codegen/binding/ResolvedBindings.java b/java/dagger/internal/codegen/binding/ResolvedBindings.java index 8a354429a..406ae41f5 100644 --- a/java/dagger/internal/codegen/binding/ResolvedBindings.java +++ b/java/dagger/internal/codegen/binding/ResolvedBindings.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; +import dagger.internal.codegen.model.ComponentPath; import dagger.internal.codegen.model.Key; /** @@ -40,6 +41,9 @@ import dagger.internal.codegen.model.Key; */ @AutoValue abstract class ResolvedBindings { + /** The component path for the resolved bindings. */ + abstract ComponentPath componentPath(); + /** The binding key for which the {@link #bindings()} have been resolved. */ abstract Key key(); @@ -127,12 +131,14 @@ abstract class ResolvedBindings { /** Creates a {@link ResolvedBindings} for contribution bindings. */ static ResolvedBindings forContributionBindings( + ComponentPath componentPath, Key key, Multimap<XTypeElement, ContributionBinding> contributionBindings, Iterable<MultibindingDeclaration> multibindings, Iterable<SubcomponentDeclaration> subcomponentDeclarations, Iterable<OptionalBindingDeclaration> optionalBindingDeclarations) { return new AutoValue_ResolvedBindings( + componentPath, key, ImmutableSetMultimap.copyOf(contributionBindings), ImmutableMap.of(), @@ -145,10 +151,12 @@ abstract class ResolvedBindings { * Creates a {@link ResolvedBindings} for members injection bindings. */ static ResolvedBindings forMembersInjectionBinding( + ComponentPath componentPath, Key key, ComponentDescriptor owningComponent, MembersInjectionBinding ownedMembersInjectionBinding) { return new AutoValue_ResolvedBindings( + componentPath, key, ImmutableSetMultimap.of(), ImmutableMap.of(owningComponent.typeElement(), ownedMembersInjectionBinding), @@ -160,8 +168,9 @@ abstract class ResolvedBindings { /** * Creates a {@link ResolvedBindings} appropriate for when there are no bindings for the key. */ - static ResolvedBindings noBindings(Key key) { + static ResolvedBindings noBindings(ComponentPath componentPath, Key key) { return new AutoValue_ResolvedBindings( + componentPath, key, ImmutableSetMultimap.of(), ImmutableMap.of(), diff --git a/java/dagger/internal/codegen/binding/SourceFiles.java b/java/dagger/internal/codegen/binding/SourceFiles.java index 19d316f3d..e8a10753d 100644 --- a/java/dagger/internal/codegen/binding/SourceFiles.java +++ b/java/dagger/internal/codegen/binding/SourceFiles.java @@ -95,11 +95,23 @@ public final class SourceFiles { return Maps.toMap( binding.dependencies(), - dependency -> - FrameworkField.create( - frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClassName(), - dependency.key().type().xprocessing().getTypeName(), - DependencyVariableNamer.name(dependency))); + dependency -> { + ClassName frameworkClassName = + frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClassName(); + // Remap factory fields back to javax.inject.Provider to maintain backwards compatibility + // for now. In a future release, we should change this to Dagger Provider. This will still + // be a breaking change, but keeping compatibility for a while should reduce the + // likelihood of breakages as it would require components built at much older versions + // using factories built at newer versions to break. + if (frameworkClassName.equals(TypeNames.DAGGER_PROVIDER)) { + frameworkClassName = TypeNames.PROVIDER; + } + return FrameworkField.create( + ParameterizedTypeName.get( + frameworkClassName, + dependency.key().type().xprocessing().getTypeName()), + DependencyVariableNamer.name(dependency)); + }); } public CodeBlock frameworkTypeUsageStatement( diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java index 774ee5d94..fd9e42efb 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java @@ -20,12 +20,13 @@ import static androidx.room.compiler.processing.compat.XConverters.getProcessing import static com.google.common.base.Verify.verify; import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.base.ElementFormatter.elementToString; +import static dagger.internal.codegen.base.Formatter.INDENT; import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey; import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey; import static dagger.internal.codegen.base.RequestKinds.canBeSatisfiedByProductionBinding; import static dagger.internal.codegen.binding.DependencyRequestFormatter.DOUBLE_INDENT; import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; @@ -38,6 +39,7 @@ import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.WildcardTypeName; +import dagger.internal.codegen.binding.ComponentNodeImpl; import dagger.internal.codegen.binding.DependencyRequestFormatter; import dagger.internal.codegen.binding.InjectBindingRegistry; import dagger.internal.codegen.model.Binding; @@ -47,7 +49,6 @@ import dagger.internal.codegen.model.BindingGraph.DependencyEdge; import dagger.internal.codegen.model.BindingGraph.Edge; import dagger.internal.codegen.model.BindingGraph.MissingBinding; import dagger.internal.codegen.model.BindingGraph.Node; -import dagger.internal.codegen.model.ComponentPath; import dagger.internal.codegen.model.DaggerAnnotation; import dagger.internal.codegen.model.DiagnosticReporter; import dagger.internal.codegen.model.Key; @@ -103,62 +104,20 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin { prunedGraph .missingBindings() .forEach( - missingBinding -> - reportMissingBinding(missingBinding, prunedGraph, fullGraph, diagnosticReporter)); + missingBinding -> reportMissingBinding(missingBinding, fullGraph, diagnosticReporter)); } private void reportMissingBinding( MissingBinding missingBinding, - BindingGraph prunedGraph, - BindingGraph fullGraph, + BindingGraph graph, DiagnosticReporter diagnosticReporter) { - ImmutableSet<Binding> wildcardAlternatives = - getSimilarTypeBindings(fullGraph, missingBinding.key()); - String wildcardTypeErrorMessage = ""; - if (!wildcardAlternatives.isEmpty()) { - wildcardTypeErrorMessage = - String.format( - "\nFound similar bindings:\n %s", - String.join( - "\n ", - wildcardAlternatives.stream() - .map( - binding -> binding.key().type() + " in [" + binding.componentPath() + "]") - .collect(toImmutableSet()))) - + "\n(In Kotlin source, a type like 'Set<Foo>' may" - + " be translated as 'Set<? extends Foo>'. To avoid this implicit" - + " conversion you can add '@JvmSuppressWildcards' on the associated type" - + " argument, e.g. 'Set<@JvmSuppressWildcards Foo>'.)"; - } - - List<ComponentPath> alternativeComponents = - wildcardAlternatives.isEmpty() - ? fullGraph.bindings(missingBinding.key()).stream() - .map(Binding::componentPath) - .distinct() - .collect(toImmutableList()) - : ImmutableList.of(); - // Print component name for each binding along the dependency path if the missing binding - // exists in a different component than expected - if (alternativeComponents.isEmpty()) { - // TODO(b/266993189): the passed in diagnostic reporter is constructed with full graph, so it - // doesn't print out full dependency path for a binding when invoking reportBinding on it. - // Therefore, we manually constructed the binding dependency path and passed into - // reportComponent. - diagnosticReporter.reportComponent( - ERROR, - fullGraph.componentNode(missingBinding.componentPath()).get(), - missingBindingErrorMessage(missingBinding, fullGraph) - + wildcardTypeErrorMessage - + "\n\nMissing binding usage:" - + diagnosticMessageGeneratorFactory.create(prunedGraph).getMessage(missingBinding)); - } else { - diagnosticReporter.reportComponent( - ERROR, - fullGraph.componentNode(missingBinding.componentPath()).get(), - missingBindingErrorMessage(missingBinding, fullGraph) - + wrongComponentErrorMessage(missingBinding, alternativeComponents, prunedGraph)); - } + diagnosticReporter.reportComponent( + ERROR, + graph.componentNode(missingBinding.componentPath()).get(), + missingBindingErrorMessage(missingBinding, graph) + + missingBindingDependencyTraceMessage(missingBinding, graph) + + alternativeBindingsMessage(missingBinding, graph) + + similarBindingsMessage(missingBinding, graph)); } private static ImmutableSet<Binding> getSimilarTypeBindings( @@ -222,50 +181,24 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin { return errorMessage.toString(); } - private String wrongComponentErrorMessage( - MissingBinding missingBinding, - List<ComponentPath> alternativeComponentPath, - BindingGraph graph) { + private String missingBindingDependencyTraceMessage( + MissingBinding missingBinding, BindingGraph graph) { ImmutableSet<DependencyEdge> entryPoints = graph.entryPointEdgesDependingOnBinding(missingBinding); DiagnosticMessageGenerator generator = diagnosticMessageGeneratorFactory.create(graph); ImmutableList<DependencyEdge> dependencyTrace = generator.dependencyTrace(missingBinding, entryPoints); StringBuilder message = - graph.isFullBindingGraph() - ? new StringBuilder() - : new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */); - // Check in which component the missing binding is requested. This can be different from the - // component the missing binding is in because we'll try to search up the parent components for - // a binding which makes missing bindings end up at the root component. This is different from - // the place we are logically requesting the binding from. Note that this is related to the - // particular dependency trace being shown and so is not necessarily stable. - String missingComponentName = - getComponentFromDependencyEdge(dependencyTrace.get(0), graph, false); - boolean hasSameComponentName = false; - for (ComponentPath component : alternativeComponentPath) { - message.append("\nNote: A binding for ").append(missingBinding.key()).append(" exists in "); - String currentComponentName = component.currentComponent().className().canonicalName(); - if (currentComponentName.contentEquals(missingComponentName)) { - hasSameComponentName = true; - message.append("[").append(component).append("]"); - } else { - message.append(currentComponentName); - } - message.append(":"); - } + new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */).append("\n"); for (DependencyEdge edge : dependencyTrace) { String line = dependencyRequestFormatter.format(edge.dependencyRequest()); if (line.isEmpty()) { continue; } - // If we ran into a rare case where the component names collide and we need to show the full - // path, only show the full path for the first dependency request. This is guaranteed to be - // the component in question since the logic for checking for a collision uses the first - // edge in the trace. Do not expand subsequent component paths to reduce spam. - String componentName = - String.format("[%s] ", getComponentFromDependencyEdge(edge, graph, hasSameComponentName)); - hasSameComponentName = false; + // We don't have to check for cases where component names collide since + // 1. We always show the full classname of the component, and + // 2. We always show the full component path at the end of the dependency trace (below). + String componentName = String.format("[%s] ", getComponentFromDependencyEdge(edge, graph)); message.append("\n").append(line.replace(DOUBLE_INDENT, DOUBLE_INDENT + componentName)); } if (!dependencyTrace.isEmpty()) { @@ -277,6 +210,71 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin { return message.toString(); } + private String alternativeBindingsMessage( + MissingBinding missingBinding, BindingGraph graph) { + ImmutableSet<Binding> alternativeBindings = graph.bindings(missingBinding.key()); + if (alternativeBindings.isEmpty()) { + return ""; + } + StringBuilder message = new StringBuilder(); + message.append("\n\nNote: ") + .append(missingBinding.key()) + .append(" is provided in the following other components:"); + for (Binding alternativeBinding : alternativeBindings) { + // Some alternative bindings appear multiple times because they were re-resolved in multiple + // components (e.g. due to multibinding contributions). To avoid the noise, we only report + // the binding where the module is contributed. + if (alternativeBinding.contributingModule().isPresent() + && !((ComponentNodeImpl) graph.componentNode(alternativeBinding.componentPath()).get()) + .componentDescriptor() + .moduleTypes() + .contains(alternativeBinding.contributingModule().get().xprocessing())) { + continue; + } + message.append("\n").append(INDENT).append(asString(alternativeBinding)); + } + return message.toString(); + } + + private String similarBindingsMessage( + MissingBinding missingBinding, BindingGraph graph) { + ImmutableSet<Binding> similarBindings = + getSimilarTypeBindings(graph, missingBinding.key()); + if (similarBindings.isEmpty()) { + return ""; + } + StringBuilder message = + new StringBuilder( + "\n\nNote: A similar binding is provided in the following other components:"); + for (Binding similarBinding : similarBindings) { + message + .append("\n") + .append(INDENT) + .append(similarBinding.key()) + .append(" is provided at:") + .append("\n") + .append(DOUBLE_INDENT) + .append(asString(similarBinding)); + } + message.append("\n") + .append( + "(For Kotlin sources, you may need to use '@JvmSuppressWildcards' or '@JvmWildcard' if " + + "you need to explicitly control the wildcards at a particular usage site.)"); + return message.toString(); + } + + private String asString(Binding binding) { + return String.format( + "[%s] %s", + binding.componentPath().currentComponent().xprocessing().getQualifiedName(), + binding.bindingElement().isPresent() + ? elementToString( + binding.bindingElement().get().xprocessing(), + /* elideMethodParameterTypes= */ true) + // For synthetic bindings just print the Binding#toString() + : binding); + } + private boolean allIncomingDependenciesCanUseProduction( MissingBinding missingBinding, BindingGraph graph) { return graph.network().inEdges(missingBinding).stream() @@ -305,15 +303,11 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin { .orElse(false); } - private static String getComponentFromDependencyEdge( - DependencyEdge edge, BindingGraph graph, boolean completePath) { - ComponentPath componentPath = graph.network().incidentNodes(edge).source().componentPath(); - return completePath - ? componentPath.toString() - : componentPath.currentComponent().className().canonicalName(); + private static String getComponentFromDependencyEdge(DependencyEdge edge, BindingGraph graph) { + return source(edge, graph).componentPath().currentComponent().className().canonicalName(); } - private Node source(Edge edge, BindingGraph graph) { + private static Node source(Edge edge, BindingGraph graph) { return graph.network().incidentNodes(edge).source(); } diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java index f767da851..30ebf09b2 100644 --- a/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java +++ b/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java @@ -30,6 +30,7 @@ import dagger.internal.codegen.model.BindingGraph; import dagger.internal.codegen.model.DiagnosticReporter; import dagger.internal.codegen.model.Key; import dagger.internal.codegen.validation.ValidationBindingGraphPlugin; +import java.util.Optional; import javax.inject.Inject; /** Validates that there are not multiple set binding contributions to the same binding. */ @@ -59,7 +60,8 @@ final class SetMultibindingValidator extends ValidationBindingGraphPlugin { Multimap<Key, Binding> dereferencedBindsTargets = HashMultimap.create(); for (Binding dep : bindingGraph.requestedBindings(binding)) { if (dep.kind().equals(DELEGATE)) { - dereferencedBindsTargets.put(dereferenceDelegateBinding(dep, bindingGraph), dep); + dereferenceDelegateBinding(dep, bindingGraph) + .ifPresent(dereferencedKey -> dereferencedBindsTargets.put(dereferencedKey, dep)); } } @@ -80,13 +82,19 @@ final class SetMultibindingValidator extends ValidationBindingGraphPlugin { }); } - /** Returns the delegate target of a delegate binding (going through other delegates as well). */ - private Key dereferenceDelegateBinding(Binding binding, BindingGraph bindingGraph) { + /** + * Returns the dereferenced key of a delegate binding (going through other delegates as well). + * + * <p>If the binding cannot be dereferenced (because it leads to a missing binding or duplicate + * bindings) then {@link Optional#empty()} is returned. + */ + private Optional<Key> dereferenceDelegateBinding(Binding binding, BindingGraph bindingGraph) { ImmutableSet<Binding> delegateSet = bindingGraph.requestedBindings(binding); - if (delegateSet.isEmpty()) { - // There may not be a delegate if the delegate is missing. In this case, we just take the - // requested key and return that. - return Iterables.getOnlyElement(binding.dependencies()).key(); + if (delegateSet.size() != 1) { + // If there isn't exactly 1 delegate then it means either a MissingBinding or DuplicateBinding + // error will be reported. Just return nothing rather than trying to dereference further, as + // anything we report here will just be noise on top of the other error anyway. + return Optional.empty(); } // If there is a binding, first we check if that is a delegate binding so we can dereference // that binding if needed. @@ -94,6 +102,6 @@ final class SetMultibindingValidator extends ValidationBindingGraphPlugin { if (delegate.kind().equals(DELEGATE)) { return dereferenceDelegateBinding(delegate, bindingGraph); } - return delegate.key(); + return Optional.of(delegate.key()); } } diff --git a/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar b/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar Binary files differindex 69e859d4c..6e4b0a96f 100644 --- a/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar +++ b/java/dagger/internal/codegen/bootstrap/bootstrap_compiler_deploy.jar diff --git a/java/dagger/internal/codegen/compileroption/CompilerOptions.java b/java/dagger/internal/codegen/compileroption/CompilerOptions.java index 42599e03b..73f7736f2 100644 --- a/java/dagger/internal/codegen/compileroption/CompilerOptions.java +++ b/java/dagger/internal/codegen/compileroption/CompilerOptions.java @@ -24,16 +24,6 @@ public abstract class CompilerOptions { public abstract boolean usesProducers(); /** - * Returns true if the experimental Android mode is enabled. - * - * <p><b>Warning: Do Not use! This flag is for internal, experimental use only!</b> - * - * <p>Issues related to this flag will not be supported. This flag could break your build, cause - * memory leaks in your app, or cause other unknown issues at runtime. - */ - public abstract boolean experimentalMergedMode(XTypeElement element); - - /** * Returns true if the fast initialization flag, {@code fastInit}, is enabled. * * <p>If enabled, the generated code will attempt to optimize for fast component initialization. diff --git a/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java b/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java index 356d55bf6..81380055a 100644 --- a/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java +++ b/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java @@ -19,7 +19,6 @@ package dagger.internal.codegen.compileroption; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Sets.immutableEnumSet; import static dagger.internal.codegen.compileroption.FeatureStatus.DISABLED; import static dagger.internal.codegen.compileroption.FeatureStatus.ENABLED; @@ -107,37 +106,14 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions } @Override - public boolean experimentalMergedMode(XTypeElement component) { - boolean isExperimental = experimentalMergedModeInternal(); - if (isExperimental) { - checkState( - !fastInitInternal(component), - "Both fast init and experimental merged mode were turned on, please specify exactly one" - + " compilation mode."); - } - return isExperimental; - } - - @Override public boolean fastInit(XTypeElement component) { - boolean isFastInit = fastInitInternal(component); - if (isFastInit) { - checkState( - !experimentalMergedModeInternal(), - "Both fast init and experimental merged mode were turned on, please specify exactly one" - + " compilation mode."); - } - return isFastInit; + return fastInitInternal(component); } private boolean fastInitInternal(XTypeElement component) { return isEnabled(FAST_INIT); } - private boolean experimentalMergedModeInternal() { - return false; - } - @Override public boolean formatGeneratedSource() { return isEnabled(FORMAT_GENERATED_SOURCE); @@ -268,6 +244,16 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions noLongerRecognized(FLOATING_BINDS_METHODS); noLongerRecognized(EXPERIMENTAL_AHEAD_OF_TIME_SUBCOMPONENTS); noLongerRecognized(USE_GRADLE_INCREMENTAL_PROCESSING); + if (!isEnabled(IGNORE_PROVISION_KEY_WILDCARDS)) { + if (processingEnv.getBackend() == XProcessingEnv.Backend.KSP) { + processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "When using KSP, you must also enable the '%s' compiler option (see %s).", + "dagger.ignoreProvisionKeyWildcards", + "https://dagger.dev/dev-guide/compiler-options#ignore-provision-key-wildcards")); + } + } return this; } @@ -359,7 +345,7 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions GENERATED_CLASS_EXTENDS_COMPONENT, - IGNORE_PROVISION_KEY_WILDCARDS, + IGNORE_PROVISION_KEY_WILDCARDS(ENABLED), VALIDATE_TRANSITIVE_COMPONENT_DEPENDENCIES(ENABLED) ; @@ -418,7 +404,7 @@ public final class ProcessingEnvironmentCompilerOptions extends CompilerOptions * How to report that an explicit binding in a subcomponent conflicts with an {@code @Inject} * constructor used in an ancestor component. */ - EXPLICIT_BINDING_CONFLICTS_WITH_INJECT(WARNING, ERROR, NONE), + EXPLICIT_BINDING_CONFLICTS_WITH_INJECT(ERROR, WARNING, NONE), ; final ValidationType defaultType; diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java index 179c411e4..a071eaeb2 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java @@ -16,9 +16,12 @@ package dagger.internal.codegen.componentgenerator; +import androidx.room.compiler.processing.XProcessingEnv; import dagger.Binds; import dagger.Module; +import dagger.Provides; import dagger.internal.codegen.base.SourceFileGenerator; +import dagger.internal.codegen.base.SourceFileHjarGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentDescriptor; @@ -29,14 +32,23 @@ public interface ComponentGeneratorModule { @Binds abstract SourceFileGenerator<BindingGraph> componentGenerator(ComponentGenerator generator); - // The HjarSourceFileGenerator wrapper first generates the entire TypeSpec before stripping out + // The SourceFileHjarGenerator wrapper first generates the entire TypeSpec before stripping out // things that aren't needed for the hjar. However, this can be really expensive for the component // because it is usually the most expensive file to generate, and most of its content is not - // needed in the hjar. Thus, instead of wrapping the ComponentGenerator in HjarSourceFileGenerator - // we provide a completely separate processing step, ComponentHjarProcessingStep, and generator, - // ComponentHjarGenerator, for when generating hjars for components, which can avoid generating - // the parts of the component that would have been stripped out by the HjarSourceFileGenerator. - @Binds - abstract SourceFileGenerator<ComponentDescriptor> componentHjarGenerator( - ComponentHjarGenerator hjarGenerator); + // needed in the hjar. Thus, we provide a completely separate processing step, + // ComponentHjarProcessingStep and ComponentHjarGenerator, for when generating hjars for + // components, which can avoid generating the parts of the component that would have been stripped + // out by the HjarSourceFileGenerator anyway. Note that we still wrap ComponentHjarGenerator in + // SourceFileHjarGenerator because it adds in constructor and method bodies that are needed for + // Javac to compile correctly, e.g. super(...) calls in the constructor and return statements in + // methods. + @Provides + static SourceFileGenerator<ComponentDescriptor> componentHjarGenerator( + XProcessingEnv processingEnv, + ComponentHjarGenerator hjarGenerator) { + // Note: technically the ComponentHjarGenerator is already in hjar form, but the + // SourceFileHjarGenerator wrapper adds in proper method bodies, e.g. constructors that require + // super() calls or methods that require return statements. + return SourceFileHjarGenerator.wrap(hjarGenerator, processingEnv); + } } diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java index 62f11632e..4214cbc72 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java @@ -192,7 +192,14 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript && isElementAccessibleFrom( module.moduleElement(), component.typeElement().getClassName().packageName())) - .map(module -> ComponentRequirement.forModule(module.moduleElement().getType()))); + .map(module -> ComponentRequirement.forModule(module.moduleElement().getType())) + // If the user hasn't defined an explicit creator/builder then we need to prune out the + // module requirements that don't require a module instance to match the non-hjar + // implementation. + .filter( + requirement -> + component.creatorDescriptor().isPresent() + || requirement.requiresModuleInstance())); } private boolean hasBindsInstanceMethods(ComponentDescriptor componentDescriptor) { diff --git a/java/dagger/internal/codegen/javac/BUILD b/java/dagger/internal/codegen/javac/BUILD index ad2f91be9..325dc99d0 100644 --- a/java/dagger/internal/codegen/javac/BUILD +++ b/java/dagger/internal/codegen/javac/BUILD @@ -23,24 +23,10 @@ java_library( name = "javac", srcs = glob(["*.java"]), plugins = ["//java/dagger/internal/codegen:component-codegen"], - exports = [ - ":javac-import", - ], deps = [ - ":javac-import", "//java/dagger:core", - "//java/dagger/internal/codegen/binding", "//java/dagger/internal/codegen/compileroption", - "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/xprocessing", "//third_party/java/guava/collect", ], ) - -load("@rules_java//java:defs.bzl", "java_import") - -# Replacement for @bazel_tools//third_party/java/jdk/langtools:javac, which seems to have gone away? -java_import( - name = "javac-import", - jars = ["@bazel_tools//third_party/java/jdk/langtools:javac_jar"], -) diff --git a/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java b/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java index 6e778357d..e7c5a44d2 100644 --- a/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java +++ b/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java @@ -37,11 +37,6 @@ final class JavacPluginCompilerOptions extends CompilerOptions { } @Override - public boolean experimentalMergedMode(XTypeElement element) { - return false; - } - - @Override public boolean fastInit(XTypeElement element) { return false; } diff --git a/java/dagger/internal/codegen/javac/JavacPluginModule.java b/java/dagger/internal/codegen/javac/JavacPluginModule.java index 519816440..f9e6da9bc 100644 --- a/java/dagger/internal/codegen/javac/JavacPluginModule.java +++ b/java/dagger/internal/codegen/javac/JavacPluginModule.java @@ -24,15 +24,13 @@ import com.sun.tools.javac.util.Context; import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.internal.codegen.binding.BindingGraphFactory; -import dagger.internal.codegen.binding.ComponentDescriptorFactory; import dagger.internal.codegen.compileroption.CompilerOptions; import javax.lang.model.util.Elements; // ALLOW_TYPES_ELEMENTS import javax.lang.model.util.Types; // ALLOW_TYPES_ELEMENTS /** - * A module that provides a {@link BindingGraphFactory} and {@link ComponentDescriptorFactory} for - * use in {@code javac} plugins. Requires a binding for the {@code javac} {@link Context}. + * A module that provides a {@link XMessager}, {@link XProcessingEnv}, and {@link CompilerOptions} + * for use in {@code javac} plugins. Requires a binding for the {@code javac} {@link Context}. */ @Module(includes = JavacPluginModule.BindsModule.class) public final class JavacPluginModule { diff --git a/java/dagger/internal/codegen/javapoet/AnnotationSpecs.java b/java/dagger/internal/codegen/javapoet/AnnotationSpecs.java index 4628802a4..50ec8b1c8 100644 --- a/java/dagger/internal/codegen/javapoet/AnnotationSpecs.java +++ b/java/dagger/internal/codegen/javapoet/AnnotationSpecs.java @@ -30,7 +30,8 @@ public final class AnnotationSpecs { RAWTYPES("rawtypes"), UNCHECKED("unchecked"), FUTURE_RETURN_VALUE_IGNORED("FutureReturnValueIgnored"), - KOTLIN_INTERNAL("KotlinInternal", "KotlinInternalInJava") + KOTLIN_INTERNAL("KotlinInternal", "KotlinInternalInJava"), + CAST("cast") ; private final ImmutableList<String> values; diff --git a/java/dagger/internal/codegen/javapoet/CodeBlocks.java b/java/dagger/internal/codegen/javapoet/CodeBlocks.java index 912dc71b8..877a0a540 100644 --- a/java/dagger/internal/codegen/javapoet/CodeBlocks.java +++ b/java/dagger/internal/codegen/javapoet/CodeBlocks.java @@ -18,7 +18,8 @@ package dagger.internal.codegen.javapoet; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.anonymousClassBuilder; -import static dagger.internal.codegen.javapoet.TypeNames.providerOf; +import static dagger.internal.codegen.javapoet.TypeNames.daggerProviderOf; +import static dagger.internal.codegen.javapoet.TypeNames.lazyOf; import static java.util.stream.StreamSupport.stream; import static javax.lang.model.element.Modifier.PUBLIC; @@ -87,7 +88,22 @@ public final class CodeBlocks { return CodeBlock.of( "$L", anonymousClassBuilder("") - .superclass(providerOf(providedType)) + .superclass(daggerProviderOf(providedType)) + .addMethod( + methodBuilder("get") + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(providedType) + .addCode(body) + .build()) + .build()); + } + + public static CodeBlock anonymousLazy(TypeName providedType, CodeBlock body) { + return CodeBlock.of( + "$L", + anonymousClassBuilder("") + .superclass(lazyOf(providedType)) .addMethod( methodBuilder("get") .addAnnotation(Override.class) diff --git a/java/dagger/internal/codegen/javapoet/TypeNames.java b/java/dagger/internal/codegen/javapoet/TypeNames.java index 105394d42..ea89fc033 100644 --- a/java/dagger/internal/codegen/javapoet/TypeNames.java +++ b/java/dagger/internal/codegen/javapoet/TypeNames.java @@ -53,6 +53,16 @@ public final class TypeNames { public static final ClassName SUBCOMPONENT_FACTORY = SUBCOMPONENT.nestedClass("Factory"); // Dagger Internal classnames + public static final ClassName IDENTIFIER_NAME_STRING = + ClassName.get("dagger.internal", "IdentifierNameString"); + public static final ClassName KEEP_FIELD_TYPE = ClassName.get("dagger.internal", "KeepFieldType"); + public static final ClassName LAZY_CLASS_KEY = + ClassName.get("dagger.multibindings", "LazyClassKey"); + public static final ClassName LAZY_CLASS_KEY_MAP = + ClassName.get("dagger.internal", "LazyClassKeyMap"); + public static final ClassName LAZY_CLASS_KEY_MAP_FACTORY = + ClassName.get("dagger.internal", "LazyClassKeyMap", "Factory"); + public static final ClassName DELEGATE_FACTORY = ClassName.get("dagger.internal", "DelegateFactory"); public static final ClassName DOUBLE_CHECK = ClassName.get("dagger.internal", "DoubleCheck"); @@ -62,6 +72,7 @@ public final class TypeNames { ClassName.get("dagger.internal", "InjectedFieldSignature"); public static final ClassName INSTANCE_FACTORY = ClassName.get("dagger.internal", "InstanceFactory"); + public static final ClassName MAP_BUILDER = ClassName.get("dagger.internal", "MapBuilder"); public static final ClassName MAP_FACTORY = ClassName.get("dagger.internal", "MapFactory"); public static final ClassName MAP_PROVIDER_FACTORY = ClassName.get("dagger.internal", "MapProviderFactory"); @@ -69,6 +80,8 @@ public final class TypeNames { public static final ClassName MEMBERS_INJECTORS = ClassName.get("dagger.internal", "MembersInjectors"); public static final ClassName PROVIDER = ClassName.get("javax.inject", "Provider"); + public static final ClassName DAGGER_PROVIDER = ClassName.get("dagger.internal", "Provider"); + public static final ClassName DAGGER_PROVIDERS = ClassName.get("dagger.internal", "Providers"); public static final ClassName PROVIDER_OF_LAZY = ClassName.get("dagger.internal", "ProviderOfLazy"); public static final ClassName SCOPE_METADATA = ClassName.get("dagger.internal", "ScopeMetadata"); @@ -81,6 +94,8 @@ public final class TypeNames { // Dagger Producers classnames public static final ClassName ABSTRACT_PRODUCER = ClassName.get("dagger.producers.internal", "AbstractProducer"); + public static final ClassName ABSTRACT_PRODUCES_METHOD_PRODUCER = + ClassName.get("dagger.producers.internal", "AbstractProducesMethodProducer"); public static final ClassName CANCELLATION_LISTENER = ClassName.get("dagger.producers.internal", "CancellationListener"); public static final ClassName CANCELLATION_POLICY = @@ -138,6 +153,8 @@ public final class TypeNames { public static final ClassName ERROR = ClassName.get("java.lang", "Error"); public static final ClassName EXCEPTION = ClassName.get("java.lang", "Exception"); public static final ClassName RUNTIME_EXCEPTION = ClassName.get("java.lang", "RuntimeException"); + public static final ClassName STRING = ClassName.get("java.lang", "String"); + public static final ClassName MAP = ClassName.get("java.util", "Map"); public static final ClassName KOTLIN_METADATA = ClassName.get("kotlin", "Metadata"); public static final ClassName IMMUTABLE_MAP = @@ -215,6 +232,10 @@ public final class TypeNames { return ParameterizedTypeName.get(PROVIDER, typeName); } + public static ParameterizedTypeName daggerProviderOf(TypeName typeName) { + return ParameterizedTypeName.get(DAGGER_PROVIDER, typeName); + } + public static ParameterizedTypeName setOf(TypeName elementType) { return ParameterizedTypeName.get(SET, elementType); } diff --git a/java/dagger/internal/codegen/kythe/BUILD b/java/dagger/internal/codegen/kythe/BUILD index 9b2e63b7a..217627d78 100644 --- a/java/dagger/internal/codegen/kythe/BUILD +++ b/java/dagger/internal/codegen/kythe/BUILD @@ -31,13 +31,13 @@ java_library( "//java/dagger/internal/codegen/javac", "//java/dagger/internal/codegen/javapoet", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/model", "//java/dagger/internal/codegen/validation", "//java/dagger/internal/codegen/xprocessing", "//third_party/java/auto:service", "//third_party/java/error_prone:annotations", "//third_party/java/guava/collect", + "//third_party/java/jsr330_inject", ], ) diff --git a/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java b/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java index 3280b4f79..0b8a5024b 100644 --- a/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java +++ b/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java @@ -40,7 +40,7 @@ import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingDeclaration; import dagger.internal.codegen.binding.BindingGraphFactory; import dagger.internal.codegen.binding.BindingNode; -import dagger.internal.codegen.binding.ComponentDescriptorFactory; +import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.ModuleDescriptor; import dagger.internal.codegen.javac.JavacPluginModule; import dagger.internal.codegen.javapoet.TypeNames; @@ -64,7 +64,7 @@ public class DaggerKythePlugin extends Plugin.Scanner<Void, Void> { // TODO(ronshapiro): use flogger private static final Logger logger = Logger.getLogger(DaggerKythePlugin.class.getCanonicalName()); private FactEmitter emitter; - @Inject ComponentDescriptorFactory componentDescriptorFactory; + @Inject ComponentDescriptor.Factory componentDescriptorFactory; @Inject BindingGraphFactory bindingGraphFactory; @Inject XProcessingEnv xProcessingEnv; diff --git a/java/dagger/internal/codegen/model/BUILD b/java/dagger/internal/codegen/model/BUILD index e6c944ba0..f497f13c0 100644 --- a/java/dagger/internal/codegen/model/BUILD +++ b/java/dagger/internal/codegen/model/BUILD @@ -27,8 +27,6 @@ java_library( "//java/dagger:core", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/xprocessing", - "//java/dagger/producers", - "//java/dagger/spi", "//third_party/java/auto:common", "//third_party/java/auto:value", "//third_party/java/guava/base", diff --git a/java/dagger/internal/codegen/model/DaggerAnnotation.java b/java/dagger/internal/codegen/model/DaggerAnnotation.java index 4a598719a..23b18e4c8 100644 --- a/java/dagger/internal/codegen/model/DaggerAnnotation.java +++ b/java/dagger/internal/codegen/model/DaggerAnnotation.java @@ -49,7 +49,7 @@ public abstract class DaggerAnnotation { return equivalenceWrapper().get(); } - public AnnotationMirror java() { + public AnnotationMirror javac() { return toJavac(xprocessing()); } diff --git a/java/dagger/internal/codegen/model/DaggerElement.java b/java/dagger/internal/codegen/model/DaggerElement.java index cf2ac52fb..2de01a18f 100644 --- a/java/dagger/internal/codegen/model/DaggerElement.java +++ b/java/dagger/internal/codegen/model/DaggerElement.java @@ -31,7 +31,7 @@ public abstract class DaggerElement { public abstract XElement xprocessing(); - public Element java() { + public Element javac() { return toJavac(xprocessing()); } diff --git a/java/dagger/internal/codegen/model/DaggerExecutableElement.java b/java/dagger/internal/codegen/model/DaggerExecutableElement.java index 484e446e2..42ce1fc97 100644 --- a/java/dagger/internal/codegen/model/DaggerExecutableElement.java +++ b/java/dagger/internal/codegen/model/DaggerExecutableElement.java @@ -32,7 +32,7 @@ public abstract class DaggerExecutableElement { public abstract XExecutableElement xprocessing(); - public ExecutableElement java() { + public ExecutableElement javac() { return toJavac(xprocessing()); } diff --git a/java/dagger/internal/codegen/model/DaggerProcessingEnv.java b/java/dagger/internal/codegen/model/DaggerProcessingEnv.java index 6645b90be..44308293a 100644 --- a/java/dagger/internal/codegen/model/DaggerProcessingEnv.java +++ b/java/dagger/internal/codegen/model/DaggerProcessingEnv.java @@ -39,7 +39,7 @@ public abstract class DaggerProcessingEnv { return Backend.valueOf(xprocessing().getBackend().name()); } - public ProcessingEnvironment java() { + public ProcessingEnvironment javac() { return toJavac(xprocessing()); } } diff --git a/java/dagger/internal/codegen/model/DaggerType.java b/java/dagger/internal/codegen/model/DaggerType.java index c606f15e0..b6f0beb72 100644 --- a/java/dagger/internal/codegen/model/DaggerType.java +++ b/java/dagger/internal/codegen/model/DaggerType.java @@ -39,7 +39,7 @@ public abstract class DaggerType { return equivalenceWrapper().get(); } - public TypeMirror java() { + public TypeMirror javac() { return toJavac(xprocessing()); } diff --git a/java/dagger/internal/codegen/model/DaggerTypeElement.java b/java/dagger/internal/codegen/model/DaggerTypeElement.java index 77e0c4ffd..1bfab067d 100644 --- a/java/dagger/internal/codegen/model/DaggerTypeElement.java +++ b/java/dagger/internal/codegen/model/DaggerTypeElement.java @@ -32,7 +32,7 @@ public abstract class DaggerTypeElement { public abstract XTypeElement xprocessing(); - public TypeElement java() { + public TypeElement javac() { return toJavac(xprocessing()); } diff --git a/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java b/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java index 9b45b891f..ade1f28e8 100644 --- a/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java +++ b/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java @@ -35,7 +35,7 @@ final class MoreAnnotationMirrors { * defined in the annotation type. */ public static String toStableString(DaggerAnnotation qualifier) { - return stableAnnotationMirrorToString(qualifier.java()); + return stableAnnotationMirrorToString(qualifier.javac()); } /** diff --git a/java/dagger/internal/codegen/model/RequestKind.java b/java/dagger/internal/codegen/model/RequestKind.java index 581a829b3..d21a04a41 100644 --- a/java/dagger/internal/codegen/model/RequestKind.java +++ b/java/dagger/internal/codegen/model/RequestKind.java @@ -19,11 +19,6 @@ package dagger.internal.codegen.model; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; -import dagger.Lazy; -import dagger.producers.Produced; -import dagger.producers.Producer; -import javax.inject.Provider; - /** * Represents the different kinds of {@link javax.lang.model.type.TypeMirror types} that may be * requested as dependencies for the same key. For example, {@code String}, {@code @@ -35,13 +30,13 @@ public enum RequestKind { /** A default request for an instance. E.g.: {@code FooType} */ INSTANCE, - /** A request for a {@link Provider}. E.g.: {@code Provider<FooType>} */ + /** A request for a {@code Provider}. E.g.: {@code Provider<FooType>} */ PROVIDER, - /** A request for a {@link Lazy}. E.g.: {@code Lazy<FooType>} */ + /** A request for a {@code Lazy}. E.g.: {@code Lazy<FooType>} */ LAZY, - /** A request for a {@link Provider} of a {@link Lazy}. E.g.: {@code Provider<Lazy<FooType>>} */ + /** A request for a {@code Provider} of a {@code Lazy}. E.g.: {@code Provider<Lazy<FooType>>}. */ PROVIDER_OF_LAZY, /** @@ -50,10 +45,10 @@ public enum RequestKind { */ MEMBERS_INJECTION, - /** A request for a {@link Producer}. E.g.: {@code Producer<FooType>} */ + /** A request for a {@code Producer}. E.g.: {@code Producer<FooType>} */ PRODUCER, - /** A request for a {@link Produced}. E.g.: {@code Produced<FooType>} */ + /** A request for a {@code Produced}. E.g.: {@code Produced<FooType>} */ PRODUCED, /** diff --git a/java/dagger/internal/codegen/processingstep/AssistedFactoryProcessingStep.java b/java/dagger/internal/codegen/processingstep/AssistedFactoryProcessingStep.java index e95bdf927..ebd5d106d 100644 --- a/java/dagger/internal/codegen/processingstep/AssistedFactoryProcessingStep.java +++ b/java/dagger/internal/codegen/processingstep/AssistedFactoryProcessingStep.java @@ -23,6 +23,7 @@ import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForB import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.INSTANCE_FACTORY; +import static dagger.internal.codegen.javapoet.TypeNames.daggerProviderOf; import static dagger.internal.codegen.javapoet.TypeNames.providerOf; import static dagger.internal.codegen.langmodel.Accessibility.accessibleTypeName; import static dagger.internal.codegen.xprocessing.MethodSpecs.overriding; @@ -311,9 +312,13 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<XTy // use the parameter names of the @AssistedFactory method. metadata.assistedInjectAssistedParameters().stream() .map(metadata.assistedFactoryAssistedParametersMap()::get) - .map(param -> CodeBlock.of("$L", getSimpleName(param))) + .map(param -> CodeBlock.of("$L", param.getJvmName())) .collect(toParametersCodeBlock())) .build()) + // In a future release, we should delete this javax method. This will still be a breaking + // change, but keeping compatibility for a while should reduce the likelihood of breakages + // as it would require components built at much older versions using factories built at + // newer versions to break. .addMethod( MethodSpec.methodBuilder("create") .addModifiers(PUBLIC, STATIC) @@ -331,6 +336,27 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<XTy : CodeBlock.of(""), name, delegateFactoryParam) + .build()) + // Normally we would have called this just "create", but because of backwards + // compatibility we can't have two methods with the same name/arguments returning + // different Provider types. + .addMethod( + MethodSpec.methodBuilder("createFactoryProvider") + .addModifiers(PUBLIC, STATIC) + .addParameter(delegateFactoryParam) + .addTypeVariables(typeVariableNames(metadata.assistedInjectElement())) + .returns(daggerProviderOf(factory.getType().getTypeName())) + .addStatement( + "return $T.$Lcreate(new $T($N))", + INSTANCE_FACTORY, + // Java 7 type inference requires the method call provide the exact type here. + isPreJava8SourceVersion(processingEnv) + ? CodeBlock.of( + "<$T>", + accessibleTypeName(metadata.factoryType(), name, processingEnv)) + : CodeBlock.of(""), + name, + delegateFactoryParam) .build()); return ImmutableList.of(builder); } diff --git a/java/dagger/internal/codegen/processingstep/AssistedInjectProcessingStep.java b/java/dagger/internal/codegen/processingstep/AssistedInjectProcessingStep.java index 8d73f4774..cfd0bec6e 100644 --- a/java/dagger/internal/codegen/processingstep/AssistedInjectProcessingStep.java +++ b/java/dagger/internal/codegen/processingstep/AssistedInjectProcessingStep.java @@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedParameter; import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.validation.InjectValidator; import dagger.internal.codegen.validation.ValidationReport; import java.util.HashSet; import java.util.Set; @@ -34,10 +35,12 @@ import javax.inject.Inject; /** An annotation processor for {@link dagger.assisted.AssistedInject}-annotated elements. */ final class AssistedInjectProcessingStep extends TypeCheckingProcessingStep<XConstructorElement> { private final XMessager messager; + private final InjectValidator injectValidator; @Inject - AssistedInjectProcessingStep(XMessager messager) { + AssistedInjectProcessingStep(XMessager messager, InjectValidator injectValidator) { this.messager = messager; + this.injectValidator = injectValidator; } @Override @@ -48,7 +51,13 @@ final class AssistedInjectProcessingStep extends TypeCheckingProcessingStep<XCon @Override protected void process( XConstructorElement assistedInjectElement, ImmutableSet<ClassName> annotations) { - new AssistedInjectValidator().validate(assistedInjectElement).printMessagesTo(messager); + // The InjectValidator has already run and reported its errors in InjectProcessingStep, so no + // need to report its errors. However, the AssistedInjectValidator relies on the InjectValidator + // returning a clean report, so we check that first before running AssistedInjectValidator. This + // shouldn't be expensive since InjectValidator caches its results after validating. + if (injectValidator.validate(assistedInjectElement.getEnclosingElement()).isClean()) { + new AssistedInjectValidator().validate(assistedInjectElement).printMessagesTo(messager); + } } private final class AssistedInjectValidator { diff --git a/java/dagger/internal/codegen/processingstep/ComponentHjarProcessingStep.java b/java/dagger/internal/codegen/processingstep/ComponentHjarProcessingStep.java index 7a7e180da..864bd768d 100644 --- a/java/dagger/internal/codegen/processingstep/ComponentHjarProcessingStep.java +++ b/java/dagger/internal/codegen/processingstep/ComponentHjarProcessingStep.java @@ -28,7 +28,6 @@ import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentDescriptor; -import dagger.internal.codegen.binding.ComponentDescriptorFactory; import dagger.internal.codegen.validation.ComponentCreatorValidator; import dagger.internal.codegen.validation.ComponentValidator; import dagger.internal.codegen.validation.ValidationReport; @@ -52,7 +51,7 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<XType private final XMessager messager; private final ComponentValidator componentValidator; private final ComponentCreatorValidator creatorValidator; - private final ComponentDescriptorFactory componentDescriptorFactory; + private final ComponentDescriptor.Factory componentDescriptorFactory; private final SourceFileGenerator<ComponentDescriptor> componentGenerator; @Inject @@ -60,7 +59,7 @@ final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<XType XMessager messager, ComponentValidator componentValidator, ComponentCreatorValidator creatorValidator, - ComponentDescriptorFactory componentDescriptorFactory, + ComponentDescriptor.Factory componentDescriptorFactory, SourceFileGenerator<ComponentDescriptor> componentGenerator) { this.messager = messager; this.componentValidator = componentValidator; diff --git a/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java b/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java index 5b17c0061..168e8946f 100644 --- a/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java +++ b/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java @@ -34,7 +34,6 @@ import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindingGraphFactory; import dagger.internal.codegen.binding.ComponentDescriptor; -import dagger.internal.codegen.binding.ComponentDescriptorFactory; import dagger.internal.codegen.validation.BindingGraphValidator; import dagger.internal.codegen.validation.ComponentCreatorValidator; import dagger.internal.codegen.validation.ComponentDescriptorValidator; @@ -52,7 +51,7 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<XTypeElem private final ComponentValidator componentValidator; private final ComponentCreatorValidator creatorValidator; private final ComponentDescriptorValidator componentDescriptorValidator; - private final ComponentDescriptorFactory componentDescriptorFactory; + private final ComponentDescriptor.Factory componentDescriptorFactory; private final BindingGraphFactory bindingGraphFactory; private final SourceFileGenerator<BindingGraph> componentGenerator; private final BindingGraphValidator bindingGraphValidator; @@ -63,7 +62,7 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<XTypeElem ComponentValidator componentValidator, ComponentCreatorValidator creatorValidator, ComponentDescriptorValidator componentDescriptorValidator, - ComponentDescriptorFactory componentDescriptorFactory, + ComponentDescriptor.Factory componentDescriptorFactory, BindingGraphFactory bindingGraphFactory, SourceFileGenerator<BindingGraph> componentGenerator, BindingGraphValidator bindingGraphValidator) { @@ -132,7 +131,10 @@ final class ComponentProcessingStep extends TypeCheckingProcessingStep<XTypeElem return; } BindingGraph fullBindingGraph = bindingGraphFactory.create(subcomponentDescriptor, true); - boolean isValid = bindingGraphValidator.isValid(fullBindingGraph.topLevelBindingGraph()); + // In this case, we don't actually care about the return value. The important part here is that + // BindingGraphValidator#isValid() runs all of the SPI plugins and reports any errors. + // TODO(bcorso): Add a separate API with no return value for this particular case. + boolean unusedIsValid = bindingGraphValidator.isValid(fullBindingGraph.topLevelBindingGraph()); } private void generateComponent(BindingGraph bindingGraph) { diff --git a/java/dagger/internal/codegen/processingstep/MonitoringModuleGenerator.java b/java/dagger/internal/codegen/processingstep/MonitoringModuleGenerator.java index 00b033d96..3692144cf 100644 --- a/java/dagger/internal/codegen/processingstep/MonitoringModuleGenerator.java +++ b/java/dagger/internal/codegen/processingstep/MonitoringModuleGenerator.java @@ -35,6 +35,7 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import dagger.Module; import dagger.internal.codegen.base.SourceFileGenerator; +import dagger.internal.codegen.binding.MonitoringModules; import dagger.internal.codegen.binding.SourceFiles; import dagger.internal.codegen.javapoet.TypeNames; import dagger.multibindings.Multibinds; @@ -42,10 +43,15 @@ import javax.inject.Inject; /** Generates a monitoring module for use with production components. */ final class MonitoringModuleGenerator extends SourceFileGenerator<XTypeElement> { + private final MonitoringModules monitoringModules; @Inject - MonitoringModuleGenerator(XFiler filer, XProcessingEnv processingEnv) { + MonitoringModuleGenerator( + XFiler filer, + XProcessingEnv processingEnv, + MonitoringModules monitoringModules) { super(filer, processingEnv); + this.monitoringModules = monitoringModules; } @Override @@ -55,6 +61,7 @@ final class MonitoringModuleGenerator extends SourceFileGenerator<XTypeElement> @Override public ImmutableList<TypeSpec.Builder> topLevelTypes(XTypeElement componentElement) { + monitoringModules.add(SourceFiles.generatedMonitoringModuleName(componentElement)); return ImmutableList.of( classBuilder(SourceFiles.generatedMonitoringModuleName(componentElement)) .addAnnotation(Module.class) diff --git a/java/dagger/internal/codegen/processingstep/TypeCheckingProcessingStep.java b/java/dagger/internal/codegen/processingstep/TypeCheckingProcessingStep.java index 5d6a64f3d..7ff47532a 100644 --- a/java/dagger/internal/codegen/processingstep/TypeCheckingProcessingStep.java +++ b/java/dagger/internal/codegen/processingstep/TypeCheckingProcessingStep.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Maps; import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.DaggerSuperficialValidation.ValidationException; +import dagger.internal.codegen.binding.MonitoringModules; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.xprocessing.XElements; import java.util.ArrayList; @@ -51,6 +52,7 @@ abstract class TypeCheckingProcessingStep<E extends XElement> implements XProces @Inject XMessager messager; @Inject CompilerOptions compilerOptions; @Inject SuperficialValidator superficialValidator; + @Inject MonitoringModules monitoringModules; @Override public final ImmutableSet<String> annotations() { @@ -70,6 +72,16 @@ abstract class TypeCheckingProcessingStep<E extends XElement> implements XProces .forEach( (element, annotations) -> { try { + if (this instanceof ComponentProcessingStep && !monitoringModules.isEmpty()) { + // If there were any monitoring modules generated by Dagger in this round then we + // should just defer processing the components until the next round. This is an + // optimization to avoid unnecessary processing of components that will likely + // need to be reprocessed next round anyway due to the missing module. We should + // be guaranteed that there will be a next round since the monitoring modules were + // generated in this round. + deferredElements.add(element); + return; + } // The XBasicAnnotationProcessor only validates the element itself. However, we // validate the enclosing type here to keep the previous behavior of // BasicAnnotationProcessor, since Dagger still relies on this behavior. diff --git a/java/dagger/internal/codegen/validation/BUILD b/java/dagger/internal/codegen/validation/BUILD index cc3670843..a24b7e0bf 100644 --- a/java/dagger/internal/codegen/validation/BUILD +++ b/java/dagger/internal/codegen/validation/BUILD @@ -36,7 +36,6 @@ java_library( "//java/dagger/internal/codegen/model", "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "//third_party/java/auto:common", "//third_party/java/auto:value", "//third_party/java/checker_framework_annotations", "//third_party/java/error_prone:annotations", diff --git a/java/dagger/internal/codegen/validation/BindingMethodValidator.java b/java/dagger/internal/codegen/validation/BindingMethodValidator.java index 9598a7c35..731050649 100644 --- a/java/dagger/internal/codegen/validation/BindingMethodValidator.java +++ b/java/dagger/internal/codegen/validation/BindingMethodValidator.java @@ -140,6 +140,7 @@ abstract class BindingMethodValidator extends BindingElementValidator<XMethodEle @Override protected final void checkAdditionalProperties() { + checkNotExtensionFunction(); checkEnclosingElement(); checkTypeParameters(); checkNotPrivate(); @@ -152,6 +153,12 @@ abstract class BindingMethodValidator extends BindingElementValidator<XMethodEle /** Checks additional properties of the binding method. */ protected void checkAdditionalMethodProperties() {} + private void checkNotExtensionFunction() { + if (method.isExtensionFunction()) { + report.addError(bindingMethods("can not be an extension function")); + } + } + /** * Adds an error if the method is not declared in a class or interface annotated with one of the * {@link #enclosingElementAnnotations}. diff --git a/java/dagger/internal/codegen/validation/ComponentValidator.java b/java/dagger/internal/codegen/validation/ComponentValidator.java index b5034480c..b3469a14e 100644 --- a/java/dagger/internal/codegen/validation/ComponentValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentValidator.java @@ -168,6 +168,7 @@ public final class ComponentValidator implements ClearableCache { // the remaining checks will likely just output unhelpful noise in such cases. return report.addError(invalidTypeError(), component).build(); } + validateFields(); validateUseOfCancellationPolicy(); validateIsAbstractType(); validateCreators(); @@ -208,6 +209,18 @@ public final class ComponentValidator implements ClearableCache { componentKind().annotation().simpleName()); } + private void validateFields() { + component.getDeclaredMethods().stream() + .filter(method -> method.isKotlinPropertySetter() && method.isAbstract()) + .forEach( + method -> + report.addError( + String.format( + "Cannot use 'abstract var' property in a component declaration to get a" + + " binding. Use 'val' or 'fun' instead: %s", + method.getPropertyName()))); + } + private void validateCreators() { ImmutableSet<XTypeElement> creators = enclosedAnnotatedTypes(component, creatorAnnotationsFor(componentAnnotation())); diff --git a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java index fc83e31f2..24ad67691 100644 --- a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java +++ b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java @@ -164,18 +164,11 @@ public final class DiagnosticMessageGenerator { ImmutableList<DependencyEdge> dependencyTrace, ImmutableSet<DependencyEdge> requests, ImmutableSet<DependencyEdge> entryPoints) { - StringBuilder message = - graph.isFullBindingGraph() - ? new StringBuilder() - : new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */); - - // Print the dependency trace unless it's a full binding graph - if (!graph.isFullBindingGraph()) { - dependencyTrace.forEach( - edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest())); - if (!dependencyTrace.isEmpty()) { - appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace))); - } + StringBuilder message = new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */); + dependencyTrace.forEach( + edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest())); + if (!dependencyTrace.isEmpty()) { + appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace))); } message.append(getRequestsNotInTrace(dependencyTrace, requests, entryPoints)); return message.toString(); @@ -190,25 +183,19 @@ public final class DiagnosticMessageGenerator { ImmutableSet<XElement> requestsToPrint = requests.stream() // if printing entry points, skip entry points and the traced request - .filter( - request -> - graph.isFullBindingGraph() - || (!request.isEntryPoint() && !isTracedRequest(dependencyTrace, request))) + .filter(request -> !request.isEntryPoint()) + .filter(request -> !isTracedRequest(dependencyTrace, request)) .map(request -> request.dependencyRequest().requestElement()) .flatMap(presentValues()) .map(DaggerElement::xprocessing) .collect(toImmutableSet()); if (!requestsToPrint.isEmpty()) { - message - .append("\nIt is") - .append(graph.isFullBindingGraph() ? " " : " also ") - .append("requested at:"); + message.append("\nIt is also requested at:"); elementFormatter.formatIndentedList(message, requestsToPrint, 1); } - // Print the remaining entry points, showing which component they're in, unless it's a full - // binding graph - if (!graph.isFullBindingGraph() && entryPoints.size() > 1) { + // Print the remaining entry points, showing which component they're in + if (entryPoints.size() > 1) { message.append("\nThe following other entry points also depend on it:"); entryPointFormatter.formatIndentedList( message, @@ -253,9 +240,14 @@ public final class DiagnosticMessageGenerator { } }; - private static boolean isTracedRequest( + private boolean isTracedRequest( ImmutableList<DependencyEdge> dependencyTrace, DependencyEdge request) { - return !dependencyTrace.isEmpty() && request.equals(dependencyTrace.get(0)); + return !dependencyTrace.isEmpty() + && request.dependencyRequest().equals(dependencyTrace.get(0).dependencyRequest()) + // Comparing the dependency request is not enough since the request is just the key. + // Instead, we check that the target incident node is the same. + && graph.network().incidentNodes(request).target() + .equals(graph.network().incidentNodes(dependencyTrace.get(0)).target()); } /** diff --git a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java index cc2d6e940..16344d3f3 100644 --- a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java +++ b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java @@ -17,6 +17,7 @@ package dagger.internal.codegen.validation; import static androidx.room.compiler.processing.XElementKt.isTypeElement; +import static androidx.room.compiler.processing.compat.XConverters.toKS; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -27,6 +28,7 @@ import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConst import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static dagger.internal.codegen.xprocessing.XTypes.nonObjectSuperclass; @@ -41,6 +43,7 @@ import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import com.google.devtools.ksp.symbol.Origin; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; import dagger.Component; @@ -82,7 +85,7 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { private final BindingFactory bindingFactory; private final CompilerOptions compilerOptions; - final class BindingsCollection<B extends Binding> { + private final class BindingsCollection<B extends Binding> { private final ClassName factoryClass; private final Map<Key, B> bindingsByKey = Maps.newLinkedHashMap(); private final Deque<B> bindingsRequiringGeneration = new ArrayDeque<>(); @@ -115,24 +118,40 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } /** Caches the binding and generates it if it needs generation. */ - void tryRegisterBinding(B binding, boolean warnIfNotAlreadyGenerated) { + void tryRegisterBinding(B binding, boolean isCalledFromInjectProcessor) { + if (processingEnv.getBackend() == XProcessingEnv.Backend.KSP) { + Origin origin = + toKS(closestEnclosingTypeElement(binding.bindingElement().get())).getOrigin(); + // If the origin of the element is from a source file in the current compilation unit then + // we're guaranteed that the InjectProcessor should run over the element so only generate + // the Factory/MembersInjector if we're being called from the InjectProcessor. + // + // TODO(bcorso): generally, this isn't something we would need to keep track of manually. + // However, KSP incremental processing has a bug that will overwrite the cache for the + // element if we generate files for it, which can lead to missing generated files from + // other processors. See https://github.com/google/dagger/issues/4063 and + // https://github.com/google/dagger/issues/4054. Remove this once that bug is fixed. + if (!isCalledFromInjectProcessor && (origin == Origin.JAVA || origin == Origin.KOTLIN)) { + return; + } + } tryToCacheBinding(binding); @SuppressWarnings("unchecked") B maybeUnresolved = binding.unresolved().isPresent() ? (B) binding.unresolved().get() : binding; - tryToGenerateBinding(maybeUnresolved, warnIfNotAlreadyGenerated); + tryToGenerateBinding(maybeUnresolved, isCalledFromInjectProcessor); } /** * Tries to generate a binding, not generating if it already is generated. For resolved * bindings, this will try to generate the unresolved version of the binding. */ - void tryToGenerateBinding(B binding, boolean warnIfNotAlreadyGenerated) { + void tryToGenerateBinding(B binding, boolean isCalledFromInjectProcessor) { if (shouldGenerateBinding(binding)) { bindingsRequiringGeneration.offer(binding); if (compilerOptions.warnIfInjectionFactoryNotGeneratedUpstream() - && warnIfNotAlreadyGenerated) { + && !isCalledFromInjectProcessor) { messager.printMessage( Kind.NOTE, String.format( @@ -147,6 +166,22 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { /** Returns true if the binding needs to be generated. */ private boolean shouldGenerateBinding(B binding) { + if (binding instanceof MembersInjectionBinding) { + MembersInjectionBinding membersInjectionBinding = (MembersInjectionBinding) binding; + // Empty members injection bindings are special and don't need source files. + if (membersInjectionBinding.injectionSites().isEmpty()) { + return false; + } + // Members injectors for classes with no local injection sites and no @Inject + // constructor are unused. + boolean hasInjectConstructor = + !(injectedConstructors(membersInjectionBinding.membersInjectedType()).isEmpty() + && assistedInjectedConstructors( + membersInjectionBinding.membersInjectedType()).isEmpty()); + if (!membersInjectionBinding.hasLocalInjectionSites() && !hasInjectConstructor) { + return false; + } + } return !binding.unresolved().isPresent() && !materializedBindingKeys.contains(binding.key()) && !bindingsRequiringGeneration.contains(binding) @@ -200,52 +235,20 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { membersInjectionBindings.generateBindings(membersInjectorGenerator); } - /** - * Registers the binding for generation and later lookup. If the binding is resolved, we also - * attempt to register an unresolved version of it. - */ - private void registerBinding(ProvisionBinding binding, boolean warnIfNotAlreadyGenerated) { - provisionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated); - } - - /** - * Registers the binding for generation and later lookup. If the binding is resolved, we also - * attempt to register an unresolved version of it. - */ - private void registerBinding(MembersInjectionBinding binding, boolean warnIfNotAlreadyGenerated) { - /* - * We generate MembersInjector classes for types with @Inject constructors only if they have any - * injection sites. - * - * We generate MembersInjector classes for types without @Inject constructors only if they have - * local (non-inherited) injection sites. - * - * Warn only when registering bindings post-hoc for those types. - */ - if (warnIfNotAlreadyGenerated) { - boolean hasInjectConstructor = - !(injectedConstructors(binding.membersInjectedType()).isEmpty() - && assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()); - warnIfNotAlreadyGenerated = - hasInjectConstructor - ? !binding.injectionSites().isEmpty() - : binding.hasLocalInjectionSites(); - } - - membersInjectionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated); - } - @Override public Optional<ProvisionBinding> tryRegisterInjectConstructor( XConstructorElement constructorElement) { - return tryRegisterConstructor(constructorElement, Optional.empty(), false); + return tryRegisterConstructor( + constructorElement, + Optional.empty(), + /* isCalledFromInjectProcessor= */ true); } @CanIgnoreReturnValue private Optional<ProvisionBinding> tryRegisterConstructor( XConstructorElement constructorElement, Optional<XType> resolvedType, - boolean warnIfNotAlreadyGenerated) { + boolean isCalledFromInjectProcessor) { XTypeElement typeElement = constructorElement.getEnclosingElement(); // Validating here shouldn't have a performance penalty because the validator caches its reports @@ -263,9 +266,9 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType); - registerBinding(binding, warnIfNotAlreadyGenerated); + provisionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor); if (!binding.injectionSites().isEmpty()) { - tryRegisterMembersInjectedType(typeElement, resolvedType, warnIfNotAlreadyGenerated); + tryRegisterMembersInjectedType(typeElement, resolvedType, isCalledFromInjectProcessor); } return Optional.of(binding); } @@ -281,7 +284,9 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { fieldElement); } return tryRegisterMembersInjectedType( - asTypeElement(fieldElement.getEnclosingElement()), Optional.empty(), false); + asTypeElement(fieldElement.getEnclosingElement()), + Optional.empty(), + /* isCalledFromInjectProcessor= */ true); } @Override @@ -295,12 +300,16 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { methodElement); } return tryRegisterMembersInjectedType( - asTypeElement(methodElement.getEnclosingElement()), Optional.empty(), false); + asTypeElement(methodElement.getEnclosingElement()), + Optional.empty(), + /* isCalledFromInjectProcessor= */ true); } @CanIgnoreReturnValue private Optional<MembersInjectionBinding> tryRegisterMembersInjectedType( - XTypeElement typeElement, Optional<XType> resolvedType, boolean warnIfNotAlreadyGenerated) { + XTypeElement typeElement, + Optional<XType> resolvedType, + boolean isCalledFromInjectProcessor) { // Validating here shouldn't have a performance penalty because the validator caches its reports ValidationReport report = injectValidator.validateForMembersInjection(typeElement); report.printMessagesTo(messager); @@ -316,7 +325,7 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType); - registerBinding(binding, warnIfNotAlreadyGenerated); + membersInjectionBindings.tryRegisterBinding(binding, isCalledFromInjectProcessor); for (Optional<XType> supertype = nonObjectSuperclass(type); supertype.isPresent(); supertype = nonObjectSuperclass(supertype.get())) { @@ -351,7 +360,12 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { assistedInjectedConstructors(element).stream()) // We're guaranteed that there's at most 1 @Inject constructors from above validation. .collect(toOptional()) - .flatMap(constructor -> tryRegisterConstructor(constructor, Optional.of(type), true)); + .flatMap( + constructor -> + tryRegisterConstructor( + constructor, + Optional.of(type), + /* isCalledFromInjectProcessor= */ false)); } @CanIgnoreReturnValue @@ -365,7 +379,9 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { return Optional.of(binding); } return tryRegisterMembersInjectedType( - key.type().xprocessing().getTypeElement(), Optional.of(key.type().xprocessing()), true); + key.type().xprocessing().getTypeElement(), + Optional.of(key.type().xprocessing()), + /* isCalledFromInjectProcessor= */ false); } @Override diff --git a/java/dagger/internal/codegen/validation/InjectValidator.java b/java/dagger/internal/codegen/validation/InjectValidator.java index f223f6a9a..ac434d7d6 100644 --- a/java/dagger/internal/codegen/validation/InjectValidator.java +++ b/java/dagger/internal/codegen/validation/InjectValidator.java @@ -64,6 +64,7 @@ import javax.tools.Diagnostic.Kind; */ @Singleton public final class InjectValidator implements ClearableCache { + private final XProcessingEnv processingEnv; private final CompilerOptions compilerOptions; private final DependencyRequestValidator dependencyRequestValidator; @@ -298,6 +299,15 @@ public final class InjectValidator implements ClearableCache { fieldElement); } + if (fieldElement.isProtected() + && fieldElement.getEnclosingElement().isFromKotlin() + ) { + builder.addItem( + "Dagger injector does not have access to kotlin protected fields", + staticMemberDiagnosticKind(), + fieldElement); + } + validateDependencyRequest(builder, fieldElement); return builder.build(); diff --git a/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java b/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java index 314ed5fb4..ae53978d5 100644 --- a/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java +++ b/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java @@ -134,8 +134,8 @@ public final class ModelBindingGraphConverter { } private static Key toModel(dagger.internal.codegen.model.Key key) { - return Key.builder(key.type().java()) - .qualifier(key.qualifier().map(DaggerAnnotation::java)) + return Key.builder(key.type().javac()) + .qualifier(key.qualifier().map(DaggerAnnotation::javac)) .multibindingContributionIdentifier( key.multibindingContributionIdentifier().isPresent() ? Optional.of(toModel(key.multibindingContributionIdentifier().get())) @@ -159,17 +159,17 @@ public final class ModelBindingGraphConverter { .key(toModel(request.key())) .isNullable(request.isNullable()); - request.requestElement().ifPresent(e -> builder.requestElement(e.java())); + request.requestElement().ifPresent(e -> builder.requestElement(e.javac())); return builder.build(); } private static Scope toModel(dagger.internal.codegen.model.Scope scope) { - return Scope.scope(scope.scopeAnnotation().java()); + return Scope.scope(scope.scopeAnnotation().javac()); } private static ComponentPath toModel(dagger.internal.codegen.model.ComponentPath path) { return ComponentPath.create( - path.components().stream().map(DaggerTypeElement::java).collect(toImmutableList())); + path.components().stream().map(DaggerTypeElement::javac).collect(toImmutableList())); } private static dagger.internal.codegen.model.BindingGraph.ComponentNode toInternal( @@ -232,8 +232,8 @@ public final class ModelBindingGraphConverter { binding.dependencies().stream() .map(ModelBindingGraphConverter::toModel) .collect(toImmutableSet()), - binding.bindingElement().map(DaggerElement::java), - binding.contributingModule().map(DaggerTypeElement::java), + binding.bindingElement().map(DaggerElement::javac), + binding.contributingModule().map(DaggerTypeElement::javac), binding.requiresModuleInstance(), binding.scope().map(ModelBindingGraphConverter::toModel), binding.isNullable(), @@ -291,7 +291,7 @@ public final class ModelBindingGraphConverter { static ChildFactoryMethodEdge create( dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge childFactoryMethodEdge) { return new AutoValue_ModelBindingGraphConverter_ChildFactoryMethodEdgeImpl( - childFactoryMethodEdge.factoryMethod().java(), childFactoryMethodEdge); + childFactoryMethodEdge.factoryMethod().javac(), childFactoryMethodEdge); } abstract dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge delegate(); @@ -310,7 +310,7 @@ public final class ModelBindingGraphConverter { subcomponentCreatorBindingEdge) { return new AutoValue_ModelBindingGraphConverter_SubcomponentCreatorBindingEdgeImpl( subcomponentCreatorBindingEdge.declaringModules().stream() - .map(DaggerTypeElement::java) + .map(DaggerTypeElement::javac) .collect(toImmutableSet()), subcomponentCreatorBindingEdge); } diff --git a/java/dagger/internal/codegen/validation/ModuleValidator.java b/java/dagger/internal/codegen/validation/ModuleValidator.java index a1d9b0d11..38bce2b44 100644 --- a/java/dagger/internal/codegen/validation/ModuleValidator.java +++ b/java/dagger/internal/codegen/validation/ModuleValidator.java @@ -58,7 +58,7 @@ import dagger.internal.codegen.base.ComponentCreatorAnnotation; import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.ModuleKind; import dagger.internal.codegen.binding.BindingGraphFactory; -import dagger.internal.codegen.binding.ComponentDescriptorFactory; +import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.binding.MethodSignatureFormatter; import dagger.internal.codegen.javapoet.TypeNames; @@ -109,7 +109,7 @@ public final class ModuleValidator { private final AnyBindingMethodValidator anyBindingMethodValidator; private final MethodSignatureFormatter methodSignatureFormatter; - private final ComponentDescriptorFactory componentDescriptorFactory; + private final ComponentDescriptor.Factory componentDescriptorFactory; private final BindingGraphFactory bindingGraphFactory; private final BindingGraphValidator bindingGraphValidator; private final InjectionAnnotations injectionAnnotations; @@ -122,7 +122,7 @@ public final class ModuleValidator { ModuleValidator( AnyBindingMethodValidator anyBindingMethodValidator, MethodSignatureFormatter methodSignatureFormatter, - ComponentDescriptorFactory componentDescriptorFactory, + ComponentDescriptor.Factory componentDescriptorFactory, BindingGraphFactory bindingGraphFactory, BindingGraphValidator bindingGraphValidator, InjectionAnnotations injectionAnnotations, diff --git a/java/dagger/internal/codegen/validation/ProducesMethodValidator.java b/java/dagger/internal/codegen/validation/ProducesMethodValidator.java index cf78b63c4..9b4c16c4e 100644 --- a/java/dagger/internal/codegen/validation/ProducesMethodValidator.java +++ b/java/dagger/internal/codegen/validation/ProducesMethodValidator.java @@ -90,7 +90,7 @@ final class ProducesMethodValidator extends BindingMethodValidator { // TODO(beder): Properly handle nullable with producer methods. private void checkNullable() { Nullability nullability = Nullability.of(method); - if (nullability.nullableAnnotation().isPresent()) { + if (!nullability.nullableAnnotations().isEmpty()) { report.addWarning("@Nullable on @Produces methods does not do anything"); } } diff --git a/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java b/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java index 75d0f7f99..42e1adffb 100644 --- a/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java +++ b/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java @@ -16,9 +16,11 @@ package dagger.internal.codegen.validation; +import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; import static androidx.room.compiler.processing.compat.XConverters.toJavac; import static androidx.room.compiler.processing.compat.XConverters.toKS; import static androidx.room.compiler.processing.compat.XConverters.toKSResolver; +import static com.google.common.base.Preconditions.checkState; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; @@ -31,6 +33,7 @@ import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.graph.EndpointPair; @@ -38,7 +41,16 @@ import com.google.common.graph.ImmutableNetwork; import com.google.common.graph.MutableNetwork; import com.google.common.graph.Network; import com.google.common.graph.NetworkBuilder; +import com.google.devtools.ksp.processing.Resolver; +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment; +import com.google.devtools.ksp.symbol.KSAnnotated; +import com.google.devtools.ksp.symbol.KSAnnotation; +import com.google.devtools.ksp.symbol.KSClassDeclaration; +import com.google.devtools.ksp.symbol.KSFunctionDeclaration; +import com.google.devtools.ksp.symbol.KSType; +import dagger.internal.codegen.xprocessing.XAnnotations; import dagger.internal.codegen.xprocessing.XElements; +import dagger.internal.codegen.xprocessing.XTypes; import dagger.spi.model.Binding; import dagger.spi.model.BindingGraph; import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge; @@ -64,6 +76,12 @@ import dagger.spi.model.Key; import dagger.spi.model.RequestKind; import dagger.spi.model.Scope; import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; /** A Utility class for converting to the {@link BindingGraph} used by external plugins. */ @@ -141,19 +159,20 @@ public final class SpiModelBindingGraphConverter { } } - private static Key toSpiModel(dagger.internal.codegen.model.Key key, XProcessingEnv env) { + private static Key toSpiModel(dagger.internal.codegen.model.Key key) { Key.Builder builder = - Key.builder(toSpiModel(key.type().xprocessing(), env)) - .qualifier(key.qualifier().map(qualifier -> toSpiModel(qualifier.xprocessing(), env))); + Key.builder(toSpiModel(key.type().xprocessing())) + .qualifier(key.qualifier().map(qualifier -> toSpiModel(qualifier.xprocessing()))); if (key.multibindingContributionIdentifier().isPresent()) { return builder .multibindingContributionIdentifier( toSpiModel( - key.multibindingContributionIdentifier().get().contributingModule().xprocessing(), - env), + key.multibindingContributionIdentifier() + .get() + .contributingModule() + .xprocessing()), toSpiModel( - key.multibindingContributionIdentifier().get().bindingMethod().xprocessing(), - env)) + key.multibindingContributionIdentifier().get().bindingMethod().xprocessing())) .build(); } return builder.build().withoutMultibindingContributionIdentifier(); @@ -169,98 +188,50 @@ public final class SpiModelBindingGraphConverter { @SuppressWarnings("CheckReturnValue") private static DependencyRequest toSpiModel( - dagger.internal.codegen.model.DependencyRequest request, XProcessingEnv env) { + dagger.internal.codegen.model.DependencyRequest request) { DependencyRequest.Builder builder = DependencyRequest.builder() .kind(toSpiModel(request.kind())) - .key(toSpiModel(request.key(), env)) + .key(toSpiModel(request.key())) .isNullable(request.isNullable()); - request - .requestElement() - .ifPresent(e -> builder.requestElement(toSpiModel(e.xprocessing(), env))); + request.requestElement().ifPresent(e -> builder.requestElement(toSpiModel(e.xprocessing()))); return builder.build(); } - private static Scope toSpiModel(dagger.internal.codegen.model.Scope scope, XProcessingEnv env) { - return Scope.scope(toSpiModel(scope.scopeAnnotation().xprocessing(), env)); + private static Scope toSpiModel(dagger.internal.codegen.model.Scope scope) { + return Scope.scope(toSpiModel(scope.scopeAnnotation().xprocessing())); } - private static ComponentPath toSpiModel( - dagger.internal.codegen.model.ComponentPath path, XProcessingEnv env) { + private static ComponentPath toSpiModel(dagger.internal.codegen.model.ComponentPath path) { return ComponentPath.create( path.components().stream() - .map(component -> toSpiModel(component.xprocessing(), env)) + .map(component -> toSpiModel(component.xprocessing())) .collect(toImmutableList())); } - private static DaggerTypeElement toSpiModel(XTypeElement typeElement, XProcessingEnv env) { - switch (env.getBackend()) { - case JAVAC: - return DaggerTypeElement.fromJavac(toJavac(typeElement)); - case KSP: - return DaggerTypeElement.fromKsp(toKS(typeElement)); - } - throw new IllegalStateException( - String.format("Backend %s is not supported yet.", env.getBackend())); + private static DaggerTypeElement toSpiModel(XTypeElement typeElement) { + return DaggerTypeElementImpl.from(typeElement); } - private static DaggerType toSpiModel(XType type, XProcessingEnv env) { - switch (env.getBackend()) { - case JAVAC: - return DaggerType.fromJavac(toJavac(type)); - case KSP: - return DaggerType.fromKsp(toKS(type)); - } - throw new IllegalStateException( - String.format("Backend %s is not supported yet.", env.getBackend())); + private static DaggerType toSpiModel(XType type) { + return DaggerTypeImpl.from(type); } - static DaggerAnnotation toSpiModel(XAnnotation annotation, XProcessingEnv env) { - DaggerTypeElement typeElement = toSpiModel(annotation.getTypeElement(), env); - - switch (env.getBackend()) { - case JAVAC: - return DaggerAnnotation.fromJavac(typeElement, toJavac(annotation)); - case KSP: - return DaggerAnnotation.fromKsp(typeElement, toKS(annotation)); - } - throw new IllegalStateException( - String.format("Backend %s is not supported yet.", env.getBackend())); + static DaggerAnnotation toSpiModel(XAnnotation annotation) { + return DaggerAnnotationImpl.from(annotation); } - private static DaggerElement toSpiModel(XElement element, XProcessingEnv env) { - switch (env.getBackend()) { - case JAVAC: - return DaggerElement.fromJavac(toJavac(element)); - case KSP: - return DaggerElement.fromKsp(XElements.toKSAnnotated(element)); - } - throw new IllegalStateException( - String.format("Backend %s is not supported yet.", env.getBackend())); + private static DaggerElement toSpiModel(XElement element) { + return DaggerElementImpl.from(element); } - private static DaggerExecutableElement toSpiModel( - XExecutableElement executableElement, XProcessingEnv env) { - switch (env.getBackend()) { - case JAVAC: - return DaggerExecutableElement.fromJava(toJavac(executableElement)); - case KSP: - return DaggerExecutableElement.fromKsp(toKS(executableElement)); - } - throw new IllegalStateException( - String.format("Backend %s is not supported yet.", env.getBackend())); + private static DaggerExecutableElement toSpiModel(XExecutableElement executableElement) { + return DaggerExecutableElementImpl.from(executableElement); } static DaggerProcessingEnv toSpiModel(XProcessingEnv env) { - switch (env.getBackend()) { - case JAVAC: - return DaggerProcessingEnv.fromJavac(toJavac(env)); - case KSP: - return DaggerProcessingEnv.fromKsp(toKS(env), toKSResolver(env)); - } - throw new IllegalStateException( - String.format("Backend %s is not supported yet.", env.getBackend())); + return DaggerProcessingEnvImpl.from(env); } private static dagger.internal.codegen.model.BindingGraph.ComponentNode toInternal( @@ -295,14 +266,14 @@ public final class SpiModelBindingGraphConverter { dagger.internal.codegen.model.BindingGraph.ComponentNode componentNode, XProcessingEnv env) { return new AutoValue_SpiModelBindingGraphConverter_ComponentNodeImpl( - toSpiModel(componentNode.componentPath(), env), + toSpiModel(componentNode.componentPath()), componentNode.isSubcomponent(), componentNode.isRealComponent(), componentNode.entryPoints().stream() - .map(request -> SpiModelBindingGraphConverter.toSpiModel(request, env)) + .map(SpiModelBindingGraphConverter::toSpiModel) .collect(toImmutableSet()), componentNode.scopes().stream() - .map(request -> SpiModelBindingGraphConverter.toSpiModel(request, env)) + .map(SpiModelBindingGraphConverter::toSpiModel) .collect(toImmutableSet()), componentNode); } @@ -319,15 +290,15 @@ public final class SpiModelBindingGraphConverter { abstract static class BindingNodeImpl implements Binding { static Binding create(dagger.internal.codegen.model.Binding binding, XProcessingEnv env) { return new AutoValue_SpiModelBindingGraphConverter_BindingNodeImpl( - toSpiModel(binding.key(), env), - toSpiModel(binding.componentPath(), env), + toSpiModel(binding.key()), + toSpiModel(binding.componentPath()), binding.dependencies().stream() - .map(request -> SpiModelBindingGraphConverter.toSpiModel(request, env)) + .map(SpiModelBindingGraphConverter::toSpiModel) .collect(toImmutableSet()), - binding.bindingElement().map(element -> toSpiModel(element.xprocessing(), env)), - binding.contributingModule().map(module -> toSpiModel(module.xprocessing(), env)), + binding.bindingElement().map(element -> toSpiModel(element.xprocessing())), + binding.contributingModule().map(module -> toSpiModel(module.xprocessing())), binding.requiresModuleInstance(), - binding.scope().map(scope -> SpiModelBindingGraphConverter.toSpiModel(scope, env)), + binding.scope().map(SpiModelBindingGraphConverter::toSpiModel), binding.isNullable(), binding.isProduction(), toSpiModel(binding.kind()), @@ -348,8 +319,8 @@ public final class SpiModelBindingGraphConverter { dagger.internal.codegen.model.BindingGraph.MissingBinding missingBinding, XProcessingEnv env) { return new AutoValue_SpiModelBindingGraphConverter_MissingBindingImpl( - toSpiModel(missingBinding.componentPath(), env), - toSpiModel(missingBinding.key(), env), + toSpiModel(missingBinding.componentPath()), + toSpiModel(missingBinding.key()), missingBinding); } @@ -369,7 +340,7 @@ public final class SpiModelBindingGraphConverter { dagger.internal.codegen.model.BindingGraph.DependencyEdge dependencyEdge, XProcessingEnv env) { return new AutoValue_SpiModelBindingGraphConverter_DependencyEdgeImpl( - toSpiModel(dependencyEdge.dependencyRequest(), env), + toSpiModel(dependencyEdge.dependencyRequest()), dependencyEdge.isEntryPoint(), dependencyEdge); } @@ -388,8 +359,7 @@ public final class SpiModelBindingGraphConverter { dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge childFactoryMethodEdge, XProcessingEnv env) { return new AutoValue_SpiModelBindingGraphConverter_ChildFactoryMethodEdgeImpl( - toSpiModel(childFactoryMethodEdge.factoryMethod().xprocessing(), env), - childFactoryMethodEdge); + toSpiModel(childFactoryMethodEdge.factoryMethod().xprocessing()), childFactoryMethodEdge); } abstract dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge internalDelegate(); @@ -409,7 +379,7 @@ public final class SpiModelBindingGraphConverter { XProcessingEnv env) { return new AutoValue_SpiModelBindingGraphConverter_SubcomponentCreatorBindingEdgeImpl( subcomponentCreatorBindingEdge.declaringModules().stream() - .map(module -> toSpiModel(module.xprocessing(), env)) + .map(module -> toSpiModel(module.xprocessing())) .collect(toImmutableSet()), subcomponentCreatorBindingEdge); } @@ -458,6 +428,225 @@ public final class SpiModelBindingGraphConverter { } } + @AutoValue + abstract static class DaggerElementImpl extends DaggerElement { + public static DaggerElement from(XElement element) { + return new AutoValue_SpiModelBindingGraphConverter_DaggerElementImpl(element); + } + + abstract XElement element(); + + @Override + public Element javac() { + checkIsJavac(backend()); + return toJavac(element()); + } + + @Override + public KSAnnotated ksp() { + checkIsKsp(backend()); + return toKS(element()); + } + + @Override + public DaggerProcessingEnv.Backend backend() { + return getBackend(getProcessingEnv(element())); + } + + @Override + public final String toString() { + return XElements.toStableString(element()); + } + } + + @AutoValue + abstract static class DaggerTypeElementImpl extends DaggerTypeElement { + public static DaggerTypeElement from(XTypeElement element) { + return new AutoValue_SpiModelBindingGraphConverter_DaggerTypeElementImpl(element); + } + + abstract XTypeElement element(); + + @Override + public TypeElement javac() { + checkIsJavac(backend()); + return toJavac(element()); + } + + @Override + public KSClassDeclaration ksp() { + checkIsKsp(backend()); + return toKS(element()); + } + + @Override + public DaggerProcessingEnv.Backend backend() { + return getBackend(getProcessingEnv(element())); + } + + @Override + public final String toString() { + return XElements.toStableString(element()); + } + } + + @AutoValue + abstract static class DaggerTypeImpl extends DaggerType { + public static DaggerType from(XType type) { + return new AutoValue_SpiModelBindingGraphConverter_DaggerTypeImpl( + XTypes.equivalence().wrap(type)); + } + + abstract Equivalence.Wrapper<XType> type(); + + @Override + public TypeMirror javac() { + checkIsJavac(backend()); + return toJavac(type().get()); + } + + @Override + public KSType ksp() { + checkIsKsp(backend()); + return toKS(type().get()); + } + + @Override + public DaggerProcessingEnv.Backend backend() { + return getBackend(getProcessingEnv(type().get())); + } + + @Override + public final String toString() { + return XTypes.toStableString(type().get()); + } + } + + @AutoValue + abstract static class DaggerAnnotationImpl extends DaggerAnnotation { + public static DaggerAnnotation from(XAnnotation annotation) { + return new AutoValue_SpiModelBindingGraphConverter_DaggerAnnotationImpl( + XAnnotations.equivalence().wrap(annotation)); + } + + abstract Equivalence.Wrapper<XAnnotation> annotation(); + + @Override + public DaggerTypeElement annotationTypeElement() { + return DaggerTypeElementImpl.from(annotation().get().getTypeElement()); + } + + @Override + public AnnotationMirror javac() { + checkIsJavac(backend()); + return toJavac(annotation().get()); + } + + @Override + public KSAnnotation ksp() { + checkIsKsp(backend()); + return toKS(annotation().get()); + } + + @Override + public DaggerProcessingEnv.Backend backend() { + return getBackend(getProcessingEnv(annotation().get())); + } + + @Override + public final String toString() { + return XAnnotations.toStableString(annotation().get()); + } + } + + @AutoValue + abstract static class DaggerExecutableElementImpl extends DaggerExecutableElement { + public static DaggerExecutableElement from(XExecutableElement executableElement) { + return new AutoValue_SpiModelBindingGraphConverter_DaggerExecutableElementImpl( + executableElement); + } + + abstract XExecutableElement executableElement(); + + @Override + public ExecutableElement javac() { + checkIsJavac(backend()); + return toJavac(executableElement()); + } + + @Override + public KSFunctionDeclaration ksp() { + checkIsKsp(backend()); + return toKS(executableElement()); + } + + @Override + public DaggerProcessingEnv.Backend backend() { + return getBackend(getProcessingEnv(executableElement())); + } + + @Override + public final String toString() { + return XElements.toStableString(executableElement()); + } + } + + private static class DaggerProcessingEnvImpl extends DaggerProcessingEnv { + private final XProcessingEnv env; + + public static DaggerProcessingEnv from(XProcessingEnv env) { + return new DaggerProcessingEnvImpl(env); + } + + DaggerProcessingEnvImpl(XProcessingEnv env) { + this.env = env; + } + + @Override + public ProcessingEnvironment javac() { + checkIsJavac(backend()); + return toJavac(env); + } + + @Override + public SymbolProcessorEnvironment ksp() { + checkIsKsp(backend()); + return toKS(env); + } + + @Override + public Resolver resolver() { + return toKSResolver(env); + } + + @Override + public DaggerProcessingEnv.Backend backend() { + return getBackend(env); + } + } + + private static void checkIsJavac(DaggerProcessingEnv.Backend backend) { + checkState( + backend == DaggerProcessingEnv.Backend.JAVAC, + "Expected JAVAC backend but was: %s", backend); + } + + private static void checkIsKsp(DaggerProcessingEnv.Backend backend) { + checkState( + backend == DaggerProcessingEnv.Backend.KSP, + "Expected KSP backend but was: %s", backend); + } + + private static DaggerProcessingEnv.Backend getBackend(XProcessingEnv env) { + switch (env.getBackend()) { + case JAVAC: + return DaggerProcessingEnv.Backend.JAVAC; + case KSP: + return DaggerProcessingEnv.Backend.KSP; + } + throw new AssertionError(String.format("Unexpected backend %s", env.getBackend())); + } + private static final class DiagnosticReporterImpl extends DiagnosticReporter { static DiagnosticReporterImpl create( dagger.internal.codegen.model.DiagnosticReporter reporter) { diff --git a/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java b/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java index 42551a003..87ac24ae8 100644 --- a/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java +++ b/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java @@ -23,10 +23,10 @@ import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import androidx.room.compiler.processing.XConstructorElement; import androidx.room.compiler.processing.XConstructorType; +import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XMethodType; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; -import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ParameterSpec; import dagger.internal.codegen.binding.AssistedInjectionAnnotations; @@ -79,12 +79,12 @@ final class AssistedInjectionParameters { } private static ImmutableList<ParameterSpec> assistedParameterSpecs( - List<? extends XVariableElement> paramElements, + List<XExecutableParameterElement> paramElements, List<XType> paramTypes, ShardImplementation shardImplementation) { ImmutableList.Builder<ParameterSpec> assistedParameterSpecs = ImmutableList.builder(); for (int i = 0; i < paramElements.size(); i++) { - XVariableElement paramElement = paramElements.get(i); + XExecutableParameterElement paramElement = paramElements.get(i); XType paramType = paramTypes.get(i); if (AssistedInjectionAnnotations.isAssistedParameter(paramElement)) { assistedParameterSpecs.add( diff --git a/java/dagger/internal/codegen/writing/BUILD b/java/dagger/internal/codegen/writing/BUILD index c8f9b61c2..3adeb66cd 100644 --- a/java/dagger/internal/codegen/writing/BUILD +++ b/java/dagger/internal/codegen/writing/BUILD @@ -31,11 +31,9 @@ java_library( "//java/dagger/internal/codegen/compileroption", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/javapoet", - "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/codegen/model", "//java/dagger/internal/codegen/xprocessing", - "//java/dagger/producers", "//third_party/java/auto:common", "//third_party/java/auto:value", "//third_party/java/error_prone:annotations", diff --git a/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java b/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java index 88d4d4123..f74b50a36 100644 --- a/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java +++ b/java/dagger/internal/codegen/writing/ComponentCreatorImplementationFactory.java @@ -202,6 +202,12 @@ final class ComponentCreatorImplementationFactory { case NEEDED: return Optional.of(normalSetterMethod(requirement)); case UNNEEDED: + // If this is a generated Builder, then remove the setter methods for modules that don't + // require an instance. + if (!componentDescriptor().creatorDescriptor().isPresent() + && !requirement.requiresModuleInstance()) { + return Optional.empty(); + } // TODO(bcorso): Don't generate noop setters for any unneeded requirements. // However, since this is a breaking change we can at least avoid trying // to generate noop setters for impossible cases like when the requirement type diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java index 204d6faec..a41b1c793 100644 --- a/java/dagger/internal/codegen/writing/ComponentImplementation.java +++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java @@ -42,6 +42,7 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import static javax.tools.Diagnostic.Kind.ERROR; +import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XMessager; import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XProcessingEnv; @@ -72,9 +73,9 @@ import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindingNode; import dagger.internal.codegen.binding.BindingRequest; +import dagger.internal.codegen.binding.CancellationPolicy; import dagger.internal.codegen.binding.ComponentCreatorDescriptor; import dagger.internal.codegen.binding.ComponentDescriptor; -import dagger.internal.codegen.binding.ComponentDescriptor.CancellationPolicy; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.binding.ComponentRequirement; import dagger.internal.codegen.binding.KeyVariableNamer; @@ -111,16 +112,11 @@ public final class ComponentImplementation { /** Compiler Modes. */ public enum CompilerMode { DEFAULT, - FAST_INIT, - EXPERIMENTAL_MERGED_MODE; + FAST_INIT; public boolean isFastInit() { return this == CompilerMode.FAST_INIT; } - - public boolean isExperimentalMergedMode() { - return this == CompilerMode.EXPERIMENTAL_MERGED_MODE; - } } /** A type of field that this component can contain. */ @@ -311,11 +307,7 @@ public final class ComponentImplementation { this.messager = messager; XTypeElement typeElement = rootComponentImplementation().componentDescriptor().typeElement(); this.compilerMode = - compilerOptions.fastInit(typeElement) - ? CompilerMode.FAST_INIT - : (compilerOptions.experimentalMergedMode(typeElement) - ? CompilerMode.EXPERIMENTAL_MERGED_MODE - : CompilerMode.DEFAULT); + compilerOptions.fastInit(typeElement) ? CompilerMode.FAST_INIT : CompilerMode.DEFAULT; } /** @@ -464,7 +456,7 @@ public final class ComponentImplementation { private final UniqueNameSet assistedParamNames = new UniqueNameSet(); private final List<CodeBlock> initializations = new ArrayList<>(); private final SwitchingProviders switchingProviders; - private final ExperimentalSwitchingProviders experimentalSwitchingProviders; + private final LazyClassKeyProviders lazyClassKeyProviders; private final Map<Key, CodeBlock> cancellations = new LinkedHashMap<>(); private final Map<XVariableElement, String> uniqueAssistedName = new LinkedHashMap<>(); private final List<CodeBlock> componentRequirementInitializations = new ArrayList<>(); @@ -481,9 +473,7 @@ public final class ComponentImplementation { private ShardImplementation(ClassName name) { this.name = name; this.switchingProviders = new SwitchingProviders(this, processingEnv); - this.experimentalSwitchingProviders = - new ExperimentalSwitchingProviders(this, componentRequestRepresentationsProvider); - + this.lazyClassKeyProviders = new LazyClassKeyProviders(this); if (graph.componentDescriptor().isProduction()) { claimMethodName(CANCELLATION_LISTENER_METHOD_NAME); } @@ -517,9 +507,8 @@ public final class ComponentImplementation { return switchingProviders; } - /** Returns the {@link ExperimentalSwitchingProviders} class for this shard. */ - public ExperimentalSwitchingProviders getExperimentalSwitchingProviders() { - return experimentalSwitchingProviders; + public LazyClassKeyProviders getLazyClassKeyProviders() { + return lazyClassKeyProviders; } /** Returns the {@link ComponentImplementation} that owns this shard. */ @@ -665,12 +654,12 @@ public final class ComponentImplementation { return assistedParamNames.getUniqueName(name); } - public String getUniqueFieldNameForAssistedParam(XVariableElement element) { - if (uniqueAssistedName.containsKey(element)) { - return uniqueAssistedName.get(element); + public String getUniqueFieldNameForAssistedParam(XExecutableParameterElement parameter) { + if (uniqueAssistedName.containsKey(parameter)) { + return uniqueAssistedName.get(parameter); } - String name = getUniqueAssistedParamName(getSimpleName(element)); - uniqueAssistedName.put(element, name); + String name = getUniqueAssistedParamName(parameter.getJvmName()); + uniqueAssistedName.put(parameter, name); return name; } diff --git a/java/dagger/internal/codegen/writing/ComponentProvisionRequestRepresentation.java b/java/dagger/internal/codegen/writing/ComponentProvisionRequestRepresentation.java index d7594cbc1..41c3c46de 100644 --- a/java/dagger/internal/codegen/writing/ComponentProvisionRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/ComponentProvisionRequestRepresentation.java @@ -36,7 +36,6 @@ final class ComponentProvisionRequestRepresentation extends RequestRepresentatio private final BindingGraph bindingGraph; private final ComponentRequirementExpressions componentRequirementExpressions; private final CompilerOptions compilerOptions; - private final boolean isExperimentalMergedMode; @AssistedInject ComponentProvisionRequestRepresentation( @@ -49,16 +48,11 @@ final class ComponentProvisionRequestRepresentation extends RequestRepresentatio this.bindingGraph = bindingGraph; this.componentRequirementExpressions = componentRequirementExpressions; this.compilerOptions = compilerOptions; - this.isExperimentalMergedMode = - componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override Expression getDependencyExpression(ClassName requestingClass) { - CodeBlock componentDependency = - isExperimentalMergedMode - ? CodeBlock.of("(($T) dependencies[0])", componentRequirement().type().getTypeName()) - : getComponentRequirementExpression(requestingClass); + CodeBlock componentDependency = getComponentRequirementExpression(requestingClass); CodeBlock invocation = CodeBlock.of( "$L.$L()", componentDependency, asMethod(binding.bindingElement().get()).getJvmName()); diff --git a/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java b/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java index 124eba1ff..dd343168a 100644 --- a/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java +++ b/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java @@ -19,7 +19,6 @@ package dagger.internal.codegen.writing; import static androidx.room.compiler.processing.XTypeKt.isVoid; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; @@ -28,13 +27,17 @@ import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessibl import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.xprocessing.MethodSpecs.overriding; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import static dagger.internal.codegen.xprocessing.XProcessingEnvs.isPreJava8SourceVersion; import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XType; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; +import dagger.internal.codegen.base.MapType; +import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindingRequest; @@ -69,11 +72,8 @@ public final class ComponentRequestRepresentations { membersInjectionBindingRepresentationFactory; private final ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory; private final ProductionBindingRepresentation.Factory productionBindingRepresentationFactory; - private final ExperimentalSwitchingProviderDependencyRepresentation.Factory - experimentalSwitchingProviderDependencyRepresentationFactory; private final Map<Binding, BindingRepresentation> representations = new HashMap<>(); - private final Map<Binding, ExperimentalSwitchingProviderDependencyRepresentation> - experimentalSwitchingProviderDependencyRepresentations = new HashMap<>(); + private final XProcessingEnv processingEnv; @Inject ComponentRequestRepresentations( @@ -84,8 +84,7 @@ public final class ComponentRequestRepresentations { MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory, ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory, ProductionBindingRepresentation.Factory productionBindingRepresentationFactory, - ExperimentalSwitchingProviderDependencyRepresentation.Factory - experimentalSwitchingProviderDependencyRepresentationFactory) { + XProcessingEnv processingEnv) { this.parent = parent; this.graph = graph; this.componentImplementation = componentImplementation; @@ -93,9 +92,8 @@ public final class ComponentRequestRepresentations { membersInjectionBindingRepresentationFactory; this.provisionBindingRepresentationFactory = provisionBindingRepresentationFactory; this.productionBindingRepresentationFactory = productionBindingRepresentationFactory; - this.experimentalSwitchingProviderDependencyRepresentationFactory = - experimentalSwitchingProviderDependencyRepresentationFactory; this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions); + this.processingEnv = processingEnv; } /** @@ -241,6 +239,15 @@ public final class ComponentRequestRepresentations { componentMethod.methodElement() .asMemberOf(componentImplementation.graph().componentTypeElement().getType()) .getReturnType(); + + // When compiling with -source 7, javac's type inference isn't strong enough to match things + // like Optional<javax.inject.Provider<T>> to Optional<dagger.internal.Provider<T>>. + if (isPreJava8SourceVersion(processingEnv) + && (MapType.isMapOfProvider(returnType) + || OptionalType.isOptionalProviderType(returnType))) { + return expression.castTo(returnType.getRawType()); + } + return !isVoid(returnType) && !expression.type().isAssignableTo(returnType) ? expression.castTo(returnType) : expression; @@ -278,29 +285,4 @@ public final class ComponentRequestRepresentations { } throw new AssertionError(); } - - /** - * Returns an {@link ExperimentalSwitchingProviderDependencyRepresentation} for the requested - * binding to satisfy dependency requests on it from experimental switching providers. Cannot be - * used for Members Injection requests. - */ - ExperimentalSwitchingProviderDependencyRepresentation - getExperimentalSwitchingProviderDependencyRepresentation(BindingRequest request) { - checkState( - componentImplementation.compilerMode().isExperimentalMergedMode(), - "Compiler mode should be experimentalMergedMode!"); - Optional<Binding> localBinding = graph.localContributionBinding(request.key()); - - if (localBinding.isPresent()) { - return reentrantComputeIfAbsent( - experimentalSwitchingProviderDependencyRepresentations, - localBinding.get(), - binding -> - experimentalSwitchingProviderDependencyRepresentationFactory.create( - (ProvisionBinding) binding)); - } - - checkArgument(parent.isPresent(), "no expression found for %s", request); - return parent.get().getExperimentalSwitchingProviderDependencyRepresentation(request); - } } diff --git a/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java b/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java index 673a659fd..82c01cfe2 100644 --- a/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java @@ -36,7 +36,9 @@ import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindsTypeChecker; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.RequestKind; +import dagger.internal.codegen.xprocessing.XTypes; /** A {@link dagger.internal.codegen.writing.RequestRepresentation} for {@code @Binds} methods. */ final class DelegateRequestRepresentation extends RequestRepresentation { @@ -88,8 +90,14 @@ final class DelegateRequestRepresentation extends RequestRepresentation { ? delegateExpression.castTo(contributedType) : delegateExpression; default: - return castToRawTypeIfNecessary( - delegateExpression, requestType(requestKind, contributedType, processingEnv)); + XType requestedType = requestType(requestKind, contributedType, processingEnv); + if (XTypes.isTypeOf(requestedType, TypeNames.PROVIDER)) { + // Even though the user may have requested a javax Provider, our generated code and + // factories only work in the Dagger Provider type, so swap to that one before doing + // a cast. + requestedType = XTypes.rewrapType(requestedType, TypeNames.DAGGER_PROVIDER); + } + return castToRawTypeIfNecessary(delegateExpression, requestedType); } } diff --git a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java index 49cc024e9..c54b03cc4 100644 --- a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java @@ -24,7 +24,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; -import static dagger.internal.codegen.javapoet.TypeNames.providerOf; +import static dagger.internal.codegen.javapoet.TypeNames.daggerProviderOf; import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.COMPONENT_PROVISION_FACTORY; import static dagger.internal.codegen.xprocessing.XElements.asMethod; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; @@ -47,7 +47,6 @@ import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import dagger.internal.codegen.xprocessing.XAnnotations; /** * A {@link javax.inject.Provider} creation expression for a provision method on a component's @@ -106,9 +105,8 @@ final class DependencyMethodProviderCreationExpression binding .nullability() - .nullableAnnotation() - .map(XAnnotations::getClassName) - .ifPresent(getMethod::addAnnotation); + .nullableAnnotations() + .forEach(getMethod::addAnnotation); // We need to use the componentShard here since the generated type is static and shards are // not static classes so it can't be nested inside the shard. @@ -123,7 +121,7 @@ final class DependencyMethodProviderCreationExpression componentShard.addType( COMPONENT_PROVISION_FACTORY, classBuilder(factoryClassName) - .addSuperinterface(providerOf(keyType)) + .addSuperinterface(daggerProviderOf(keyType)) .addModifiers(PRIVATE, STATIC, FINAL) .addField(dependencyClassName, dependency().variableName(), PRIVATE, FINAL) .addMethod( diff --git a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java index 4095a600d..f054e6a7e 100644 --- a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java @@ -24,11 +24,13 @@ import com.squareup.javapoet.ClassName; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.binding.BindsTypeChecker; import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.BindingKind; import dagger.internal.codegen.model.RequestKind; @@ -59,30 +61,61 @@ final class DerivedFromFrameworkInstanceRequestRepresentation extends RequestRep @Override Expression getDependencyExpression(ClassName requestingClass) { - Expression expression = - frameworkType.to( - requestKind, - frameworkRequestRepresentation.getDependencyExpression(requestingClass), - processingEnv); - return requiresTypeCast(expression, requestingClass) - ? expression.castTo(binding.contributedType()) - : expression; + return getDependencyExpressionFromFrameworkExpression( + frameworkRequestRepresentation.getDependencyExpression(requestingClass), + requestingClass); } @Override Expression getDependencyExpressionForComponentMethod( ComponentMethodDescriptor componentMethod, ComponentImplementation component) { + return getDependencyExpressionFromFrameworkExpression( + frameworkRequestRepresentation + .getDependencyExpressionForComponentMethod(componentMethod, component), + component.name()); + } + + private Expression getDependencyExpressionFromFrameworkExpression( + Expression frameworkExpression, ClassName requestingClass) { Expression expression = frameworkType.to( requestKind, - frameworkRequestRepresentation.getDependencyExpressionForComponentMethod( - componentMethod, component), + frameworkExpression, processingEnv); - return requiresTypeCast(expression, component.name()) + + // If it is a map type we need to do a raw type cast. This is because a user requested field + // type like dagger.internal.Provider<Map<K, javax.inject.Provider<V>>> isn't always assignable + // from something like dagger.internal.Provider<Map<K, dagger.internal.Provider<V>>> just due + // to variance issues. + if (MapType.isMapOfProvider(binding.contributedType())) { + return castMapOfProvider(expression, binding); + } + + return requiresTypeCast(expression, requestingClass) ? expression.castTo(binding.contributedType()) : expression; } + private Expression castMapOfProvider(Expression expression, ContributionBinding binding) { + switch (requestKind) { + case INSTANCE: + return expression.castTo(binding.contributedType()); + case PROVIDER: + case PROVIDER_OF_LAZY: + return expression.castTo(processingEnv.requireType(TypeNames.DAGGER_PROVIDER).getRawType()); + case LAZY: + return expression.castTo(processingEnv.requireType(TypeNames.LAZY).getRawType()); + case PRODUCER: + case FUTURE: + return expression.castTo(processingEnv.requireType(TypeNames.PRODUCER).getRawType()); + case PRODUCED: + return expression.castTo(processingEnv.requireType(TypeNames.PRODUCED).getRawType()); + + case MEMBERS_INJECTION: // fall through + } + throw new IllegalStateException("Unexpected request kind: " + requestKind); + } + private boolean requiresTypeCast(Expression expression, ClassName requestingClass) { return binding.kind().equals(BindingKind.DELEGATE) && requestKind.equals(RequestKind.INSTANCE) diff --git a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java deleted file mode 100644 index 1ebbb240b..000000000 --- a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2021 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.internal.codegen.writing; - -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible; -import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; -import static dagger.internal.codegen.xprocessing.XTypes.rewrapType; - -import androidx.room.compiler.processing.XProcessingEnv; -import androidx.room.compiler.processing.XType; -import com.squareup.javapoet.CodeBlock; -import dagger.assisted.Assisted; -import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; -import dagger.internal.codegen.base.ContributionType; -import dagger.internal.codegen.binding.BindsTypeChecker; -import dagger.internal.codegen.binding.FrameworkType; -import dagger.internal.codegen.binding.ProvisionBinding; -import dagger.internal.codegen.javapoet.Expression; -import dagger.internal.codegen.javapoet.TypeNames; -import dagger.internal.codegen.model.BindingKind; -import dagger.internal.codegen.model.DependencyRequest; -import dagger.internal.codegen.model.RequestKind; -import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; - -/** - * Returns type casted expressions to satisfy dependency requests from experimental switching - * providers. - */ -final class ExperimentalSwitchingProviderDependencyRepresentation { - private final ProvisionBinding binding; - private final ShardImplementation shardImplementation; - private final BindsTypeChecker bindsTypeChecker; - private final XProcessingEnv processingEnv; - private final XType type; - - @AssistedInject - ExperimentalSwitchingProviderDependencyRepresentation( - @Assisted ProvisionBinding binding, - ComponentImplementation componentImplementation, - BindsTypeChecker bindsTypeChecker, - XProcessingEnv processingEnv) { - this.binding = binding; - this.shardImplementation = componentImplementation.shardImplementation(binding); - this.processingEnv = processingEnv; - this.bindsTypeChecker = bindsTypeChecker; - this.type = - isDelegateSetValuesBinding() - // For convience we allow @Binds @ElementsIntoSet from Collection => Set so that List - // can be contributed without converting to a Set first. Thus, here we rewrap the - // contributed type from Set<T> => Collection<T> to reflect this. - ? rewrapType(binding.contributedType(), TypeNames.COLLECTION) - : binding.contributedType(); - } - - Expression getDependencyExpression(RequestKind requestKind, ProvisionBinding requestingBinding) { - int index = findIndexOfDependency(requestingBinding); - XType frameworkType = - processingEnv.getDeclaredType( - processingEnv.requireTypeElement(FrameworkType.PROVIDER.frameworkClassName())); - Expression expression = - FrameworkType.PROVIDER.to( - requestKind, - Expression.create( - frameworkType, - CodeBlock.of( - "(($T) dependencies[$L])", frameworkType.getRawType().getTypeName(), index)), - processingEnv); - if (usesExplicitTypeCast(expression, requestKind)) { - return expression.castTo(type); - } - if (usesRawTypeCast(requestKind)) { - return expression.castTo(type.getRawType()); - } - return expression; - } - - private int findIndexOfDependency(ProvisionBinding requestingBinding) { - return requestingBinding.dependencies().stream() - .map(DependencyRequest::key) - .collect(toImmutableList()) - .indexOf(binding.key()) - + (requestingBinding.requiresModuleInstance() - && requestingBinding.contributingModule().isPresent() - ? 1 - : 0); - } - - private boolean isDelegateSetValuesBinding() { - return binding.kind().equals(BindingKind.DELEGATE) - && binding.contributionType().equals(ContributionType.SET_VALUES); - } - - private boolean usesExplicitTypeCast(Expression expression, RequestKind requestKind) { - // If the type is accessible, we can directly cast the expression use the type. - return requestKind.equals(RequestKind.INSTANCE) - && !bindsTypeChecker.isAssignable(expression.type(), type, binding.contributionType()) - && isTypeAccessibleFrom(type, shardImplementation.name().packageName()); - } - - private boolean usesRawTypeCast(RequestKind requestKind) { - // If a type has inaccessible type arguments, then cast to raw type. - return requestKind.equals(RequestKind.INSTANCE) - && !isTypeAccessibleFrom(type, shardImplementation.name().packageName()) - && isRawTypeAccessible(type, shardImplementation.name().packageName()); - } - - @AssistedFactory - static interface Factory { - ExperimentalSwitchingProviderDependencyRepresentation create(ProvisionBinding binding); - } -} diff --git a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java deleted file mode 100644 index 586b5f53b..000000000 --- a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2022 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.writing; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Iterables.getLast; -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.squareup.javapoet.MethodSpec.methodBuilder; -import static com.squareup.javapoet.TypeSpec.classBuilder; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; -import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; -import static dagger.internal.codegen.javapoet.TypeNames.providerOf; -import static javax.lang.model.element.Modifier.FINAL; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.PUBLIC; -import static javax.lang.model.element.Modifier.STATIC; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.squareup.javapoet.TypeVariableName; -import dagger.internal.codegen.binding.ProvisionBinding; -import dagger.internal.codegen.javapoet.CodeBlocks; -import dagger.internal.codegen.model.Key; -import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; -import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.TreeMap; -import javax.inject.Provider; - -/** - * Keeps track of all provider expression requests for a component. - * - * <p>The provider expression request will be satisfied by a single generated {@code Provider} class - * that can provide instances for all types by switching on an id. - */ -final class ExperimentalSwitchingProviders { - /** - * Each switch size is fixed at 100 cases each and put in its own method. This is to limit the - * size of the methods so that we don't reach the "huge" method size limit for Android that will - * prevent it from being AOT compiled in some versions of Android (b/77652521). This generally - * starts to happen around 1500 cases, but we are choosing 100 to be safe. - */ - // TODO(bcorso): Include a proguard_spec in the Dagger library to prevent inlining these methods? - // TODO(ronshapiro): Consider making this configurable via a flag. - private static final int MAX_CASES_PER_SWITCH = 100; - - private static final long MAX_CASES_PER_CLASS = MAX_CASES_PER_SWITCH * MAX_CASES_PER_SWITCH; - private static final TypeVariableName T = TypeVariableName.get("T"); - - /** - * Maps a {@link Key} to an instance of a {@link SwitchingProviderBuilder}. Each group of {@code - * MAX_CASES_PER_CLASS} keys will share the same instance. - */ - private final Map<Key, SwitchingProviderBuilder> switchingProviderBuilders = - new LinkedHashMap<>(); - - private final ShardImplementation shardImplementation; - private final Provider<ComponentRequestRepresentations> componentRequestRepresentationsProvider; - - ExperimentalSwitchingProviders( - ShardImplementation shardImplementation, - Provider<ComponentRequestRepresentations> componentRequestRepresentationsProvider) { - this.shardImplementation = checkNotNull(shardImplementation); - this.componentRequestRepresentationsProvider = - checkNotNull(componentRequestRepresentationsProvider); - } - - /** Returns the framework instance creation expression for an inner switching provider class. */ - FrameworkInstanceCreationExpression newFrameworkInstanceCreationExpression( - ProvisionBinding binding, RequestRepresentation unscopedInstanceRequestRepresentation) { - return new FrameworkInstanceCreationExpression() { - @Override - public CodeBlock creationExpression() { - return switchingProviderBuilders - .computeIfAbsent(binding.key(), key -> getSwitchingProviderBuilder()) - .getNewInstanceCodeBlock(binding, unscopedInstanceRequestRepresentation); - } - }; - } - - private SwitchingProviderBuilder getSwitchingProviderBuilder() { - if (switchingProviderBuilders.size() % MAX_CASES_PER_CLASS == 0) { - String name = shardImplementation.getUniqueClassName("SwitchingProvider"); - // TODO(wanyingd): move Switching Providers and injection methods to Shard classes to avoid - // exceeding component class constant pool limit. - SwitchingProviderBuilder switchingProviderBuilder = - new SwitchingProviderBuilder(shardImplementation.name().nestedClass(name)); - shardImplementation.addTypeSupplier(switchingProviderBuilder::build); - return switchingProviderBuilder; - } - return getLast(switchingProviderBuilders.values()); - } - - // TODO(bcorso): Consider just merging this class with ExperimentalSwitchingProviders. - private final class SwitchingProviderBuilder { - // Keep the switch cases ordered by switch id. The switch Ids are assigned in pre-order - // traversal, but the switch cases are assigned in post-order traversal of the binding graph. - private final Map<Integer, CodeBlock> switchCases = new TreeMap<>(); - private final Map<Key, Integer> switchIds = new HashMap<>(); - private final ClassName switchingProviderType; - - SwitchingProviderBuilder(ClassName switchingProviderType) { - this.switchingProviderType = checkNotNull(switchingProviderType); - } - - private CodeBlock getNewInstanceCodeBlock( - ProvisionBinding binding, RequestRepresentation unscopedInstanceRequestRepresentation) { - Key key = binding.key(); - if (!switchIds.containsKey(key)) { - int switchId = switchIds.size(); - switchIds.put(key, switchId); - switchCases.put( - switchId, createSwitchCaseCodeBlock(key, unscopedInstanceRequestRepresentation)); - } - - CodeBlock switchingProviderDependencies; - switch (binding.kind()) { - // TODO(wanyingd): there might be a better way to get component requirement information - // without using unscopedInstanceRequestRepresentation. - case COMPONENT_PROVISION: - switchingProviderDependencies = - ((ComponentProvisionRequestRepresentation) unscopedInstanceRequestRepresentation) - .getComponentRequirementExpression(shardImplementation.name()); - break; - case SUBCOMPONENT_CREATOR: - switchingProviderDependencies = - ((SubcomponentCreatorRequestRepresentation) unscopedInstanceRequestRepresentation) - .getDependencyExpressionArguments(); - break; - case MULTIBOUND_SET: - case MULTIBOUND_MAP: - case OPTIONAL: - case INJECTION: - case PROVISION: - case ASSISTED_FACTORY: - // Arguments built in the order of module reference, provision dependencies and members - // injection dependencies - switchingProviderDependencies = - componentRequestRepresentationsProvider.get().getCreateMethodArgumentsCodeBlock( - binding, shardImplementation.name()); - break; - default: - throw new IllegalArgumentException("Unexpected binding kind: " + binding.kind()); - } - - return CodeBlock.of( - "new $T<$L>($L)", - switchingProviderType, - // Add the type parameter explicitly when the binding is scoped because Java can't resolve - // the type when wrapped. For example, the following will error: - // fooProvider = DoubleCheck.provider(new SwitchingProvider<>(1)); - CodeBlock.of("$T", shardImplementation.accessibleTypeName(binding.contributedType())), - switchingProviderDependencies.isEmpty() - ? CodeBlock.of("$L", switchIds.get(key)) - : CodeBlock.of("$L, $L", switchIds.get(key), switchingProviderDependencies)); - } - - private CodeBlock createSwitchCaseCodeBlock( - Key key, RequestRepresentation unscopedInstanceRequestRepresentation) { - // TODO(bcorso): Try to delay calling getDependencyExpression() until we are writing out the - // SwitchingProvider because calling it here makes FrameworkFieldInitializer think there's a - // cycle when initializing ExperimentalSwitchingProviders which adds an unnecessary - // DelegateFactory. - CodeBlock instanceCodeBlock = - unscopedInstanceRequestRepresentation - .getDependencyExpression(switchingProviderType) - .box() - .codeBlock(); - - return CodeBlock.builder() - // TODO(bcorso): Is there something else more useful than the key? - .add("case $L: // $L \n", switchIds.get(key), key) - .addStatement("return ($T) $L", T, instanceCodeBlock) - .build(); - } - - private TypeSpec build() { - TypeSpec.Builder builder = - classBuilder(switchingProviderType) - .addModifiers(PRIVATE, FINAL, STATIC) - .addTypeVariable(T) - .addSuperinterface(providerOf(T)) - .addMethods(getMethods()); - - // The SwitchingProvider constructor lists switch id first and then the dependency array. - MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); - builder.addField(TypeName.INT, "id", PRIVATE, FINAL); - constructor.addParameter(TypeName.INT, "id").addStatement("this.id = id"); - // Pass in provision dependencies and members injection dependencies. - builder.addField(Object[].class, "dependencies", FINAL, PRIVATE); - constructor - .addParameter(Object[].class, "dependencies") - .addStatement("this.dependencies = dependencies") - .varargs(true); - - return builder.addMethod(constructor.build()).build(); - } - - private ImmutableList<MethodSpec> getMethods() { - ImmutableList<CodeBlock> switchCodeBlockPartitions = switchCodeBlockPartitions(); - if (switchCodeBlockPartitions.size() == 1) { - // There are less than MAX_CASES_PER_SWITCH cases, so no need for extra get methods. - return ImmutableList.of( - methodBuilder("get") - .addModifiers(PUBLIC) - .addAnnotation(suppressWarnings(UNCHECKED)) - .addAnnotation(Override.class) - .returns(T) - .addCode(getOnlyElement(switchCodeBlockPartitions)) - .build()); - } - - // This is the main public "get" method that will route to private getter methods. - MethodSpec.Builder routerMethod = - methodBuilder("get") - .addModifiers(PUBLIC) - .addAnnotation(Override.class) - .returns(T) - .beginControlFlow("switch (id / $L)", MAX_CASES_PER_SWITCH); - - ImmutableList.Builder<MethodSpec> getMethods = ImmutableList.builder(); - for (int i = 0; i < switchCodeBlockPartitions.size(); i++) { - MethodSpec method = - methodBuilder("get" + i) - .addModifiers(PRIVATE) - .addAnnotation(suppressWarnings(UNCHECKED)) - .returns(T) - .addCode(switchCodeBlockPartitions.get(i)) - .build(); - getMethods.add(method); - routerMethod.addStatement("case $L: return $N()", i, method); - } - - routerMethod.addStatement("default: throw new $T(id)", AssertionError.class).endControlFlow(); - - return getMethods.add(routerMethod.build()).build(); - } - - private ImmutableList<CodeBlock> switchCodeBlockPartitions() { - return Lists.partition(ImmutableList.copyOf(switchCases.values()), MAX_CASES_PER_SWITCH) - .stream() - .map( - partitionCases -> - CodeBlock.builder() - .beginControlFlow("switch (id)") - .add(CodeBlocks.concat(partitionCases)) - .addStatement("default: throw new $T(id)", AssertionError.class) - .endControlFlow() - .build()) - .collect(toImmutableList()); - } - } -} diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java index 9ec13146d..32195ba6a 100644 --- a/java/dagger/internal/codegen/writing/FactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java @@ -35,16 +35,15 @@ import static dagger.internal.codegen.javapoet.TypeNames.factoryOf; import static dagger.internal.codegen.model.BindingKind.INJECTION; import static dagger.internal.codegen.model.BindingKind.PROVISION; import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; -import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XFiler; import androidx.room.compiler.processing.XProcessingEnv; -import androidx.room.compiler.processing.XVariableElement; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -71,7 +70,6 @@ import dagger.internal.codegen.model.Key; import dagger.internal.codegen.model.Scope; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod; -import dagger.internal.codegen.xprocessing.XAnnotations; import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -236,7 +234,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding UniqueNameSet uniqueFieldNames = new UniqueNameSet(); ImmutableMap<DependencyRequest, FieldSpec> frameworkFields = frameworkFields(binding); frameworkFields.values().forEach(field -> uniqueFieldNames.claim(field.name)); - ImmutableMap<XVariableElement, ParameterSpec> assistedParameters = + ImmutableMap<XExecutableParameterElement, ParameterSpec> assistedParameters = assistedParameters(binding).stream() .collect( toImmutableMap( @@ -244,13 +242,12 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding parameter -> ParameterSpec.builder( parameter.getType().getTypeName(), - uniqueFieldNames.getUniqueName(getSimpleName(parameter))) + uniqueFieldNames.getUniqueName(parameter.getJvmName())) .build())); TypeName providedTypeName = providedTypeName(binding); MethodSpec.Builder getMethod = methodBuilder("get") .addModifiers(PUBLIC) - .returns(providedTypeName) .addParameters(assistedParameters.values()); if (factoryTypeName(binding).isPresent()) { @@ -270,13 +267,14 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding if (binding.kind().equals(PROVISION)) { binding .nullability() - .nullableAnnotation() - .map(XAnnotations::getClassName) - .ifPresent(getMethod::addAnnotation); + .nullableAnnotations() + .forEach(getMethod::addAnnotation); + getMethod.returns(providedTypeName); getMethod.addStatement("return $L", invokeNewInstance); } else if (!binding.injectionSites().isEmpty()) { CodeBlock instance = CodeBlock.of("instance"); getMethod + .returns(providedTypeName) .addStatement("$T $L = $L", providedTypeName, instance, invokeNewInstance) .addCode( InjectionSiteMethod.invokeAll( @@ -286,8 +284,11 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding binding.key().type().xprocessing(), sourceFiles.frameworkFieldUsages(binding.dependencies(), frameworkFields)::get)) .addStatement("return $L", instance); + } else { - getMethod.addStatement("return $L", invokeNewInstance); + getMethod + .returns(providedTypeName) + .addStatement("return $L", invokeNewInstance); } return getMethod.build(); } diff --git a/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java b/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java index 5a981773e..32f0dde04 100644 --- a/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java +++ b/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java @@ -142,8 +142,9 @@ class FrameworkFieldInitializer implements FrameworkInstanceSupplier { FrameworkField.forBinding( binding, frameworkInstanceCreationExpression.alternativeFrameworkClass()); - TypeName fieldType = - useRawType ? contributionBindingField.type().rawType : contributionBindingField.type(); + TypeName fieldType = useRawType + ? TypeNames.rawTypeName(contributionBindingField.type()) + : contributionBindingField.type(); if (binding.kind() == BindingKind.ASSISTED_INJECTION) { // An assisted injection factory doesn't extend Provider, so we reference the generated diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java b/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java index bb62d52fe..037df56d6 100644 --- a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java +++ b/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java @@ -47,7 +47,6 @@ final class FrameworkInstanceBindingRepresentation { FrameworkInstanceBindingRepresentation( @Assisted ProvisionBinding binding, BindingGraph graph, - @Assisted FrameworkInstanceSupplier providerField, ComponentImplementation componentImplementation, DelegateRequestRepresentation.Factory delegateRequestRepresentationFactory, DerivedFromFrameworkInstanceRequestRepresentation.Factory @@ -65,7 +64,7 @@ final class FrameworkInstanceBindingRepresentation { this.providerRequestRepresentation = binding.kind().equals(DELEGATE) && !needsCaching(binding, graph) ? delegateRequestRepresentationFactory.create(binding, RequestKind.PROVIDER) - : providerInstanceRequestRepresentationFactory.create(binding, providerField); + : providerInstanceRequestRepresentationFactory.create(binding); this.producerFromProviderRepresentation = producerNodeInstanceRequestRepresentationFactory.create( binding, @@ -108,7 +107,6 @@ final class FrameworkInstanceBindingRepresentation { @AssistedFactory static interface Factory { - FrameworkInstanceBindingRepresentation create( - ProvisionBinding binding, FrameworkInstanceSupplier providerField); + FrameworkInstanceBindingRepresentation create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java b/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java index 1353e887f..8e9d4c67d 100644 --- a/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java +++ b/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java @@ -19,21 +19,17 @@ package dagger.internal.codegen.writing; import static dagger.internal.codegen.model.BindingKind.DELEGATE; import dagger.internal.codegen.binding.ContributionBinding; -import dagger.internal.codegen.model.BindingKind; import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode; /** Generation mode for satisfying framework request to Provision Binding. */ enum FrameworkInstanceKind { SWITCHING_PROVIDER, - EXPERIMENTAL_SWITCHING_PROVIDER, STATIC_FACTORY, PROVIDER_FIELD; public static FrameworkInstanceKind from(ContributionBinding binding, CompilerMode compilerMode) { if (usesSwitchingProvider(binding, compilerMode)) { - if (compilerMode.isExperimentalMergedMode()) { - return EXPERIMENTAL_SWITCHING_PROVIDER; - } else if (compilerMode.isFastInit()) { + if (compilerMode.isFastInit()) { return SWITCHING_PROVIDER; } else { throw new IllegalStateException( @@ -48,12 +44,7 @@ enum FrameworkInstanceKind { private static boolean usesSwitchingProvider( ContributionBinding binding, CompilerMode compilerMode) { - if (!compilerMode.isFastInit() && !compilerMode.isExperimentalMergedMode()) { - return false; - } - // TODO(wanyingd): remove this check once we allow inaccessible types in merged mode. - if (compilerMode.isExperimentalMergedMode() - && binding.kind().equals(BindingKind.ASSISTED_FACTORY)) { + if (!compilerMode.isFastInit()) { return false; } switch (binding.kind()) { @@ -100,10 +91,9 @@ enum FrameworkInstanceKind { return true; case PROVISION: return !compilerMode.isFastInit() - && !compilerMode.isExperimentalMergedMode() && !binding.requiresModuleInstance(); case INJECTION: - return !compilerMode.isFastInit() && !compilerMode.isExperimentalMergedMode(); + return !compilerMode.isFastInit(); default: return false; } diff --git a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java deleted file mode 100644 index 4bafb5aff..000000000 --- a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2017 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.writing; - -import static com.squareup.javapoet.MethodSpec.constructorBuilder; -import static com.squareup.javapoet.MethodSpec.methodBuilder; -import static com.squareup.javapoet.TypeSpec.classBuilder; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static javax.lang.model.element.Modifier.PRIVATE; - -import androidx.room.compiler.processing.XElement; -import com.google.common.collect.ImmutableList; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeSpec; -import dagger.internal.codegen.base.SourceFileGenerator; -import javax.lang.model.element.Modifier; - -/** - * A source file generator that only writes the relevant code necessary for Bazel to create a - * correct header (ABI) jar. - */ -public final class HjarSourceFileGenerator<T> extends SourceFileGenerator<T> { - private final SourceFileGenerator<T> delegate; - - private HjarSourceFileGenerator(SourceFileGenerator<T> delegate) { - super(delegate); - this.delegate = delegate; - } - - public static <T> SourceFileGenerator<T> wrap(SourceFileGenerator<T> delegate) { - return new HjarSourceFileGenerator<>(delegate); - } - - @Override - public XElement originatingElement(T input) { - return delegate.originatingElement(input); - } - - @Override - public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) { - return delegate.topLevelTypes(input).stream() - .map(completeType -> skeletonType(completeType.build())) - .collect(toImmutableList()); - } - - private TypeSpec.Builder skeletonType(TypeSpec completeType) { - TypeSpec.Builder skeleton = - classBuilder(completeType.name) - .addSuperinterfaces(completeType.superinterfaces) - .addTypeVariables(completeType.typeVariables) - .addModifiers(completeType.modifiers.toArray(new Modifier[0])) - .addAnnotations(completeType.annotations); - - if (!completeType.superclass.equals(ClassName.OBJECT)) { - skeleton.superclass(completeType.superclass); - } - - completeType.methodSpecs.stream() - .filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor()) - .map(this::skeletonMethod) - .forEach(skeleton::addMethod); - - completeType.fieldSpecs.stream() - .filter(field -> !field.modifiers.contains(PRIVATE)) - .map(this::skeletonField) - .forEach(skeleton::addField); - - completeType.typeSpecs.stream() - .map(type -> skeletonType(type).build()) - .forEach(skeleton::addType); - - return skeleton; - } - - private MethodSpec skeletonMethod(MethodSpec completeMethod) { - MethodSpec.Builder skeleton = - completeMethod.isConstructor() - ? constructorBuilder() - : methodBuilder(completeMethod.name).returns(completeMethod.returnType); - - if (completeMethod.isConstructor()) { - // Code in Turbine must (for technical reasons in javac) have a valid super() call for - // constructors, otherwise javac will bark, and Turbine has no way to avoid this. So we retain - // constructor method bodies if they do exist - skeleton.addCode(completeMethod.code); - } - - return skeleton - .addModifiers(completeMethod.modifiers) - .addTypeVariables(completeMethod.typeVariables) - .addParameters(completeMethod.parameters) - .addExceptions(completeMethod.exceptions) - .varargs(completeMethod.varargs) - .addAnnotations(completeMethod.annotations) - .build(); - } - - private FieldSpec skeletonField(FieldSpec completeField) { - return FieldSpec.builder( - completeField.type, - completeField.name, - completeField.modifiers.toArray(new Modifier[0])) - .addAnnotations(completeField.annotations) - .build(); - } -} diff --git a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java index e43d8446d..3eaf92db0 100644 --- a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java +++ b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java @@ -26,10 +26,13 @@ import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XFiler; import androidx.room.compiler.processing.XProcessingEnv; import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.MapKeys; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.model.DaggerAnnotation; import javax.inject.Inject; /** @@ -56,11 +59,31 @@ public final class InaccessibleMapKeyProxyGenerator public ImmutableList<TypeSpec.Builder> topLevelTypes(ContributionBinding binding) { return MapKeys.mapKeyFactoryMethod(binding, processingEnv) .map( - method -> - classBuilder(MapKeys.mapKeyProxyClassName(binding)) - .addModifiers(PUBLIC, FINAL) - .addMethod(constructorBuilder().addModifiers(PRIVATE).build()) - .addMethod(method)) + method -> { + TypeSpec.Builder builder = + classBuilder(MapKeys.mapKeyProxyClassName(binding)) + .addModifiers(PUBLIC, FINAL) + .addMethod(constructorBuilder().addModifiers(PRIVATE).build()) + .addMethod(method); + // In proguard, we need to keep the classes referenced by @LazyClassKey, we do that by + // generating a field referencing the type, and then applying @KeepFieldType to the + // field. Here, we generate the field in the proxy class. For classes that are + // accessible from the dagger component, we generate fields in LazyClassKeyProvider. + // Note: the generated field should not be initialized to avoid class loading. + binding + .mapKey() + .map(DaggerAnnotation::xprocessing) + .filter( + mapKey -> + mapKey.getTypeElement().getClassName().equals(TypeNames.LAZY_CLASS_KEY)) + .map( + mapKey -> + FieldSpec.builder(mapKey.getAsType("value").getTypeName(), "className") + .addAnnotation(TypeNames.KEEP_FIELD_TYPE) + .build()) + .ifPresent(builder::addField); + return builder; + }) .map(ImmutableList::of) .orElse(ImmutableList.of()); } diff --git a/java/dagger/internal/codegen/writing/InjectionMethods.java b/java/dagger/internal/codegen/writing/InjectionMethods.java index 75b950aa3..39c03dd7f 100644 --- a/java/dagger/internal/codegen/writing/InjectionMethods.java +++ b/java/dagger/internal/codegen/writing/InjectionMethods.java @@ -18,6 +18,7 @@ package dagger.internal.codegen.writing; import static androidx.room.compiler.processing.XElementKt.isConstructor; import static androidx.room.compiler.processing.XElementKt.isMethod; +import static androidx.room.compiler.processing.XElementKt.isMethodParameter; import static androidx.room.compiler.processing.XTypeKt.isVoid; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; @@ -27,7 +28,6 @@ import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAss 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.binding.SourceFiles.protectAgainstKeywords; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; @@ -81,7 +81,6 @@ import dagger.internal.codegen.xprocessing.XAnnotations; import java.util.List; import java.util.Optional; import java.util.function.Function; -import javax.lang.model.SourceVersion; /** Convenience methods for creating and invoking {@link InjectionMethod}s. */ final class InjectionMethods { @@ -145,7 +144,7 @@ final class InjectionMethods { static CodeBlock invoke( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, - Function<XVariableElement, String> uniqueAssistedParameterName, + Function<XExecutableParameterElement, String> uniqueAssistedParameterName, ClassName requestingClass, Optional<CodeBlock> moduleReference, CompilerOptions compilerOptions) { @@ -162,7 +161,7 @@ final class InjectionMethods { static ImmutableList<CodeBlock> invokeArguments( ProvisionBinding binding, Function<DependencyRequest, CodeBlock> dependencyUsage, - Function<XVariableElement, String> uniqueAssistedParameterName) { + Function<XExecutableParameterElement, String> uniqueAssistedParameterName) { ImmutableMap<XExecutableParameterElement, DependencyRequest> dependencyRequestMap = binding.provisionDependencies().stream() .collect( @@ -406,10 +405,10 @@ final class InjectionMethods { if (isVoid(method.getReturnType())) { return builder.addStatement("$L", invocation).build(); } else { - Nullability.of(method) - .nullableAnnotation() - .map(XAnnotations::getClassName) - .ifPresent(builder::addAnnotation); + Nullability nullability = Nullability.of(method); + nullability + .nullableAnnotations() + .forEach(builder::addAnnotation); return builder .returns(method.getReturnType().getTypeName()) .addStatement("return $L", invocation) @@ -463,7 +462,11 @@ final class InjectionMethods { return parameters.stream() .map( parameter -> { - String name = parameterNameSet.getUniqueName(validJavaName(getSimpleName(parameter))); + String name = + parameterNameSet.getUniqueName( + isMethodParameter(parameter) + ? asMethodParameter(parameter).getJvmName() + : getSimpleName(parameter)); boolean useObject = !isRawTypePubliclyAccessible(parameter.getType()); return copyParameter(methodBuilder, parameter.getType(), name, useObject); }) @@ -487,19 +490,4 @@ final class InjectionMethods { // If we had to cast the instance add an extra parenthesis incase we're calling a method on it. return useObject ? CodeBlock.of("($L)", instance) : instance; } - - private static String validJavaName(CharSequence name) { - if (SourceVersion.isIdentifier(name)) { - return protectAgainstKeywords(name.toString()); - } - - StringBuilder newName = new StringBuilder(name.length()); - char firstChar = name.charAt(0); - if (!Character.isJavaIdentifierStart(firstChar)) { - newName.append('_'); - } - - name.chars().forEach(c -> newName.append(Character.isJavaIdentifierPart(c) ? c : '_')); - return newName.toString(); - } } diff --git a/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java b/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java index d32531bef..27e017dba 100644 --- a/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java @@ -18,8 +18,15 @@ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; +import static dagger.internal.codegen.model.BindingKind.ASSISTED_FACTORY; import static dagger.internal.codegen.model.BindingKind.INJECTION; +import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; @@ -29,6 +36,7 @@ import dagger.internal.codegen.javapoet.CodeBlocks; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; +import java.util.Optional; import javax.inject.Provider; /** @@ -42,32 +50,64 @@ final class InjectionOrProvisionProviderCreationExpression private final ContributionBinding binding; private final ShardImplementation shardImplementation; private final ComponentRequestRepresentations componentRequestRepresentations; + private final XProcessingEnv processingEnv; @AssistedInject InjectionOrProvisionProviderCreationExpression( @Assisted ContributionBinding binding, ComponentImplementation componentImplementation, - ComponentRequestRepresentations componentRequestRepresentations) { + ComponentRequestRepresentations componentRequestRepresentations, + XProcessingEnv processingEnv) { this.binding = checkNotNull(binding); this.shardImplementation = componentImplementation.shardImplementation(binding); this.componentRequestRepresentations = componentRequestRepresentations; + this.processingEnv = processingEnv; } @Override public CodeBlock creationExpression() { + ClassName factoryImpl = generatedClassNameForBinding(binding); CodeBlock createFactory = CodeBlock.of( - "$T.create($L)", - generatedClassNameForBinding(binding), + "$T.$L($L)", + factoryImpl, + // A different name is used for assisted factories due to backwards compatibility + // issues when migrating from the javax Provider. + binding.kind().equals(ASSISTED_FACTORY) ? "createFactoryProvider" : "create", componentRequestRepresentations.getCreateMethodArgumentsCodeBlock( binding, shardImplementation.name())); + // If this is for an AssistedFactory, then we may need to change the call in case we're building + // against a library built at an older version of Dagger before the changes to make factories + // return a Dagger Provider instead of a javax.inject.Provider. + if (binding.kind().equals(ASSISTED_FACTORY)) { + XTypeElement factoryType = processingEnv.findTypeElement(factoryImpl); + // If we can't find the factory, then assume it is being generated this run, which means + // it should be the newer version and not need wrapping. If it is missing for some other + // reason, then that likely means there will just be some other compilation failure. + if (factoryType != null) { + Optional<XMethodElement> createMethod = factoryType.getDeclaredMethods().stream() + .filter(method -> method.isStatic() + && getSimpleName(method).equals("createFactoryProvider")) + .collect(toOptional()); + // Only convert it if the newer method doesn't exist. + if (createMethod.isEmpty()) { + createFactory = CodeBlock.of( + "$T.asDaggerProvider($T.create($L))", + TypeNames.DAGGER_PROVIDERS, + factoryImpl, + componentRequestRepresentations.getCreateMethodArgumentsCodeBlock( + binding, shardImplementation.name())); + } + } + } + // When scoping a parameterized factory for an @Inject class, Java 7 cannot always infer the // type properly, so cast to a raw framework type before scoping. if (binding.kind().equals(INJECTION) && binding.unresolved().isPresent() && binding.scope().isPresent()) { - return CodeBlocks.cast(createFactory, TypeNames.PROVIDER); + return CodeBlocks.cast(createFactory, TypeNames.DAGGER_PROVIDER); } else { return createFactory; } diff --git a/java/dagger/internal/codegen/writing/LazyClassKeyProviders.java b/java/dagger/internal/codegen/writing/LazyClassKeyProviders.java new file mode 100644 index 000000000..2c2581172 --- /dev/null +++ b/java/dagger/internal/codegen/writing/LazyClassKeyProviders.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 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.writing; + +import static dagger.internal.codegen.base.MapKeyAccessibility.isMapKeyAccessibleFrom; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.STATIC; + +import androidx.room.compiler.processing.XAnnotation; +import com.google.common.base.Preconditions; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.TypeSpec; +import dagger.internal.codegen.base.UniqueNameSet; +import dagger.internal.codegen.javapoet.TypeNames; +import dagger.internal.codegen.model.Key; +import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; +import java.util.HashMap; +import java.util.Map; + +/** Keeps track of all providers for DaggerMap keys. */ +public final class LazyClassKeyProviders { + public static final String MAP_KEY_PROVIDER_NAME = "LazyClassKeyProvider"; + private final ClassName mapKeyProviderType; + private final Map<Key, FieldSpec> entries = new HashMap<>(); + private final Map<Key, FieldSpec> keepClassNamesFields = new HashMap<>(); + private final UniqueNameSet uniqueFieldNames = new UniqueNameSet(); + private final ShardImplementation shardImplementation; + private boolean providerAdded = false; + + LazyClassKeyProviders(ShardImplementation shardImplementation) { + String name = shardImplementation.getUniqueClassName(MAP_KEY_PROVIDER_NAME); + mapKeyProviderType = shardImplementation.name().nestedClass(name); + this.shardImplementation = shardImplementation; + } + + /** Returns a reference to a field in LazyClassKeyProvider that corresponds to this binding. */ + CodeBlock getMapKeyExpression(Key key) { + // This is for avoid generating empty LazyClassKeyProvider in codegen tests + if (!providerAdded) { + shardImplementation.addTypeSupplier(this::build); + providerAdded = true; + } + if (!entries.containsKey(key)) { + addField(key); + } + return CodeBlock.of("$T.$N", mapKeyProviderType, entries.get(key)); + } + + private void addField(Key key) { + Preconditions.checkArgument( + key.multibindingContributionIdentifier().isPresent() + && key.multibindingContributionIdentifier() + .get() + .bindingMethod() + .xprocessing() + .hasAnnotation(TypeNames.LAZY_CLASS_KEY)); + XAnnotation lazyClassKeyAnnotation = + key.multibindingContributionIdentifier() + .get() + .bindingMethod() + .xprocessing() + .getAnnotation(TypeNames.LAZY_CLASS_KEY); + ClassName lazyClassKey = + lazyClassKeyAnnotation.getAsType("value").getTypeElement().getClassName(); + entries.put( + key, + FieldSpec.builder( + TypeNames.STRING, + uniqueFieldNames.getUniqueName(lazyClassKey.canonicalName().replace('.', '_'))) + // TODO(b/217435141): Leave the field as non-final. We will apply @IdentifierNameString + // on the field, which doesn't work well with static final fields. + .addModifiers(STATIC) + .initializer("$S", lazyClassKey.reflectionName()) + .build()); + // To be able to apply -includedescriptorclasses rule to keep the class names referenced by + // LazyClassKey, we need to generate fields that uses those classes as type in + // LazyClassKeyProvider. For types that are not accessible from the generated component, we + // generate fields in the proxy class. + // Note: the generated field should not be initialized to avoid class loading. + if (isMapKeyAccessibleFrom(lazyClassKeyAnnotation, shardImplementation.name().packageName())) { + keepClassNamesFields.put( + key, + FieldSpec.builder( + lazyClassKey, + uniqueFieldNames.getUniqueName(lazyClassKey.canonicalName().replace('.', '_'))) + .addAnnotation(TypeNames.KEEP_FIELD_TYPE) + .build()); + } + } + + private TypeSpec build() { + TypeSpec.Builder builder = + TypeSpec.classBuilder(mapKeyProviderType) + .addAnnotation(TypeNames.IDENTIFIER_NAME_STRING) + .addModifiers(PRIVATE, STATIC, FINAL) + .addFields(entries.values()) + .addFields(keepClassNamesFields.values()); + return builder.build(); + } +} diff --git a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java index 1f857dbf1..0ea382d1f 100644 --- a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java +++ b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java @@ -22,14 +22,16 @@ import static dagger.internal.codegen.binding.SourceFiles.mapFactoryClassName; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import androidx.room.compiler.processing.XProcessingEnv; -import androidx.room.compiler.processing.XType; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.TypeName; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.binding.MapKeys; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DependencyRequest; import java.util.stream.Stream; @@ -41,6 +43,8 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr private final ComponentImplementation componentImplementation; private final BindingGraph graph; private final ContributionBinding binding; + private final boolean useLazyClassKey; + private final LazyClassKeyProviders lazyClassKeyProviders; @AssistedInject MapFactoryCreationExpression( @@ -54,22 +58,31 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr this.binding = checkNotNull(binding); this.componentImplementation = componentImplementation; this.graph = graph; + this.useLazyClassKey = MapKeys.useLazyClassKey(binding, graph); + this.lazyClassKeyProviders = + componentImplementation.shardImplementation(binding).getLazyClassKeyProviders(); } @Override public CodeBlock creationExpression() { - CodeBlock.Builder builder = CodeBlock.builder().add("$T.", mapFactoryClassName(binding)); + ClassName mapFactoryClassName = mapFactoryClassName(binding); + CodeBlock.Builder builder = CodeBlock.builder().add("$T.", mapFactoryClassName); + TypeName valueTypeName = TypeName.OBJECT; if (!useRawType()) { MapType mapType = MapType.from(binding.key()); // TODO(ronshapiro): either inline this into mapFactoryClassName, or add a // mapType.unwrappedValueType() method that doesn't require a framework type - XType valueType = + valueTypeName = Stream.of(TypeNames.PROVIDER, TypeNames.PRODUCER, TypeNames.PRODUCED) .filter(mapType::valuesAreTypeOf) .map(mapType::unwrappedValueType) .collect(toOptional()) - .orElseGet(mapType::valueType); - builder.add("<$T, $T>", mapType.keyType().getTypeName(), valueType.getTypeName()); + .orElseGet(mapType::valueType) + .getTypeName(); + builder.add( + "<$T, $T>", + useLazyClassKey ? TypeNames.STRING : mapType.keyType().getTypeName(), + valueTypeName); } builder.add("builder($L)", binding.dependencies().size()); @@ -78,12 +91,20 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr ContributionBinding contributionBinding = graph.contributionBinding(dependency.key()); builder.add( ".put($L, $L)", - getMapKeyExpression(contributionBinding, componentImplementation.name(), processingEnv), + useLazyClassKey + ? lazyClassKeyProviders.getMapKeyExpression(dependency.key()) + : getMapKeyExpression( + contributionBinding, componentImplementation.name(), processingEnv), multibindingDependencyExpression(dependency)); } - builder.add(".build()"); - return builder.build(); + return useLazyClassKey + ? CodeBlock.of( + "$T.<$T>of($L)", + TypeNames.LAZY_CLASS_KEY_MAP_FACTORY, + valueTypeName, + builder.add(".build()").build()) + : builder.add(".build()").build(); } @AssistedFactory diff --git a/java/dagger/internal/codegen/writing/MapRequestRepresentation.java b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java index bb49ebe99..f631d34ce 100644 --- a/java/dagger/internal/codegen/writing/MapRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java @@ -38,6 +38,7 @@ import dagger.internal.MapBuilder; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ContributionBinding; +import dagger.internal.codegen.binding.MapKeys; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.javapoet.TypeNames; @@ -54,7 +55,8 @@ final class MapRequestRepresentation extends RequestRepresentation { private final ProvisionBinding binding; private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies; private final ComponentRequestRepresentations componentRequestRepresentations; - private final boolean isExperimentalMergedMode; + private final boolean useLazyClassKey; + private final LazyClassKeyProviders lazyClassKeyProviders; @AssistedInject MapRequestRepresentation( @@ -70,12 +72,29 @@ final class MapRequestRepresentation extends RequestRepresentation { this.componentRequestRepresentations = componentRequestRepresentations; this.dependencies = Maps.toMap(binding.dependencies(), dep -> graph.contributionBinding(dep.key())); - this.isExperimentalMergedMode = - componentImplementation.compilerMode().isExperimentalMergedMode(); + this.useLazyClassKey = MapKeys.useLazyClassKey(binding, graph); + this.lazyClassKeyProviders = + componentImplementation.shardImplementation(binding).getLazyClassKeyProviders(); } @Override Expression getDependencyExpression(ClassName requestingClass) { + MapType mapType = MapType.from(binding.key()); + Expression dependencyExpression = getUnderlyingMapExpression(requestingClass); + // LazyClassKey is backed with a string map, therefore needs to be wrapped. + if (useLazyClassKey) { + return Expression.create( + dependencyExpression.type(), + CodeBlock.of( + "$T.<$T>of($L)", + TypeNames.LAZY_CLASS_KEY_MAP, + mapType.valueType().getTypeName(), + dependencyExpression.codeBlock())); + } + return dependencyExpression; + } + + private Expression getUnderlyingMapExpression(ClassName requestingClass) { // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory boolean isImmutableMapAvailable = isImmutableMapAvailable(); // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available? @@ -87,9 +106,7 @@ final class MapRequestRepresentation extends RequestRepresentation { .add(maybeTypeParameters(requestingClass)) .add( "of($L)", - dependencies - .keySet() - .stream() + dependencies.keySet().stream() .map(dependency -> keyAndValueExpression(dependency, requestingClass)) .collect(toParametersCodeBlock())) .build()); @@ -104,10 +121,10 @@ final class MapRequestRepresentation extends RequestRepresentation { "singletonMap($L)", keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass))); default: - CodeBlock.Builder instantiation = CodeBlock.builder(); - instantiation - .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class) - .add(maybeTypeParameters(requestingClass)); + CodeBlock.Builder instantiation = + CodeBlock.builder() + .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class) + .add(maybeTypeParameters(requestingClass)); if (isImmutableMapBuilderWithExpectedSizeAvailable()) { instantiation.add("builderWithExpectedSize($L)", dependencies.size()); } else if (isImmutableMapAvailable) { @@ -135,16 +152,12 @@ final class MapRequestRepresentation extends RequestRepresentation { private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) { return CodeBlock.of( "$L, $L", - getMapKeyExpression(dependencies.get(dependency), requestingClass, processingEnv), - isExperimentalMergedMode - ? componentRequestRepresentations - .getExperimentalSwitchingProviderDependencyRepresentation( - bindingRequest(dependency)) - .getDependencyExpression(dependency.kind(), binding) - .codeBlock() - : componentRequestRepresentations - .getDependencyExpression(bindingRequest(dependency), requestingClass) - .codeBlock()); + useLazyClassKey + ? lazyClassKeyProviders.getMapKeyExpression(dependency.key()) + : getMapKeyExpression(dependencies.get(dependency), requestingClass, processingEnv), + componentRequestRepresentations + .getDependencyExpression(bindingRequest(dependency), requestingClass) + .codeBlock()); } private Expression collectionsStaticFactoryInvocation( @@ -163,7 +176,9 @@ final class MapRequestRepresentation extends RequestRepresentation { MapType mapType = MapType.from(binding.key()); return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName()) ? CodeBlock.of( - "<$T, $T>", mapType.keyType().getTypeName(), mapType.valueType().getTypeName()) + "<$T, $T>", + useLazyClassKey ? TypeNames.STRING : mapType.keyType().getTypeName(), + mapType.valueType().getTypeName()) : CodeBlock.of(""); } diff --git a/java/dagger/internal/codegen/writing/MembersInjectionMethods.java b/java/dagger/internal/codegen/writing/MembersInjectionMethods.java index bac7490b4..a00e3676f 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectionMethods.java +++ b/java/dagger/internal/codegen/writing/MembersInjectionMethods.java @@ -16,15 +16,12 @@ package dagger.internal.codegen.writing; -import static com.google.common.base.Preconditions.checkState; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.MEMBERS_INJECTION_METHOD; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.STATIC; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XType; @@ -52,7 +49,6 @@ import javax.inject.Inject; @PerComponentImplementation final class MembersInjectionMethods { private final Map<Key, Expression> injectMethodExpressions = new LinkedHashMap<>(); - private final Map<Key, Expression> experimentalInjectMethodExpressions = new LinkedHashMap<>(); private final ComponentImplementation componentImplementation; private final ComponentRequestRepresentations bindingExpressions; private final BindingGraph graph; @@ -81,7 +77,7 @@ final class MembersInjectionMethods { : graph.localContributionBinding(key).get(); Expression expression = reentrantComputeIfAbsent( - injectMethodExpressions, key, k -> injectMethodExpression(binding, false)); + injectMethodExpressions, key, k -> injectMethodExpression(binding)); ShardImplementation shardImplementation = componentImplementation.shardImplementation(binding); return Expression.create( expression.type(), @@ -94,32 +90,11 @@ final class MembersInjectionMethods { instance)); } - /** - * Returns the members injection {@link Expression} for the given {@link Key}, creating it if - * necessary. - */ - Expression getInjectExpressionExperimental( - ProvisionBinding provisionBinding, CodeBlock instance, ClassName requestingClass) { - checkState( - componentImplementation.compilerMode().isExperimentalMergedMode(), - "Compiler mode should be experimentalMergedMode!"); - Expression expression = - reentrantComputeIfAbsent( - experimentalInjectMethodExpressions, - provisionBinding.key(), - k -> injectMethodExpression(provisionBinding, true)); - return Expression.create( - expression.type(), CodeBlock.of("$L($L, dependencies)", expression.codeBlock(), instance)); - } - - private Expression injectMethodExpression(Binding binding, boolean useStaticInjectionMethod) { + private Expression injectMethodExpression(Binding binding) { // TODO(wanyingd): move Switching Providers and injection methods to Shard classes to avoid // exceeding component class constant pool limit. // Add to Component Shard so that is can be accessible from Switching Providers. - ShardImplementation shardImplementation = - useStaticInjectionMethod - ? componentImplementation.getComponentShard() - : componentImplementation.shardImplementation(binding); + ShardImplementation shardImplementation = componentImplementation.shardImplementation(binding); XType keyType = binding.key().type().xprocessing(); XType membersInjectedType = isTypeAccessibleFrom(keyType, shardImplementation.name().packageName()) @@ -132,16 +107,10 @@ final class MembersInjectionMethods { ParameterSpec parameter = ParameterSpec.builder(membersInjectedType.getTypeName(), "instance").build(); MethodSpec.Builder methodBuilder = - useStaticInjectionMethod - ? methodBuilder(methodName) - .addModifiers(PRIVATE, STATIC) - .returns(membersInjectedType.getTypeName()) - .addParameter(parameter) - .addParameter(Object[].class, "dependencies") - : methodBuilder(methodName) - .addModifiers(PRIVATE) - .returns(membersInjectedType.getTypeName()) - .addParameter(parameter); + methodBuilder(methodName) + .addModifiers(PRIVATE) + .returns(membersInjectedType.getTypeName()) + .addParameter(parameter); XTypeElement canIgnoreReturnValue = processingEnv.findTypeElement("com.google.errorprone.annotations.CanIgnoreReturnValue"); if (canIgnoreReturnValue != null) { @@ -155,23 +124,14 @@ final class MembersInjectionMethods { instance, membersInjectedType, request -> - (useStaticInjectionMethod - ? bindingExpressions - .getExperimentalSwitchingProviderDependencyRepresentation( - bindingRequest(request)) - .getDependencyExpression(request.kind(), (ProvisionBinding) binding) - : bindingExpressions.getDependencyArgumentExpression( - request, shardImplementation.name())) + bindingExpressions + .getDependencyArgumentExpression(request, shardImplementation.name()) .codeBlock())); methodBuilder.addStatement("return $L", instance); MethodSpec method = methodBuilder.build(); shardImplementation.addMethod(MEMBERS_INJECTION_METHOD, method); - return Expression.create( - membersInjectedType, - useStaticInjectionMethod - ? CodeBlock.of("$T.$N", shardImplementation.name(), method) - : CodeBlock.of("$N", method)); + return Expression.create(membersInjectedType, CodeBlock.of("$N", method)); } private static ImmutableSet<InjectionSite> injectionSites(Binding binding) { diff --git a/java/dagger/internal/codegen/writing/MembersInjectionRequestRepresentation.java b/java/dagger/internal/codegen/writing/MembersInjectionRequestRepresentation.java index 79b8802b9..f8f31a36e 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectionRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/MembersInjectionRequestRepresentation.java @@ -17,7 +17,6 @@ package dagger.internal.codegen.writing; import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import androidx.room.compiler.processing.XExecutableParameterElement; import androidx.room.compiler.processing.XMethodElement; @@ -56,7 +55,7 @@ final class MembersInjectionRequestRepresentation extends RequestRepresentation XMethodElement methodElement = componentMethod.methodElement(); XExecutableParameterElement parameter = getOnlyElement(methodElement.getParameters()); return membersInjectionMethods.getInjectExpression( - binding.key(), CodeBlock.of("$L", getSimpleName(parameter)), component.name()); + binding.key(), CodeBlock.of("$L", parameter.getJvmName()), component.name()); } // TODO(bcorso): Consider making this a method on all RequestRepresentations. diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java index 82d12ddb5..36a991591 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java +++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java @@ -20,8 +20,6 @@ import static com.google.common.base.Preconditions.checkState; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; -import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; -import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; @@ -90,19 +88,6 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI @Override public ImmutableList<TypeSpec.Builder> topLevelTypes(MembersInjectionBinding binding) { - // Empty members injection bindings are special and don't need source files. - if (binding.injectionSites().isEmpty()) { - return ImmutableList.of(); - } - - // Members injectors for classes with no local injection sites and no @Inject - // constructor are unused. - if (!binding.hasLocalInjectionSites() - && injectedConstructors(binding.membersInjectedType()).isEmpty() - && assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()) { - return ImmutableList.of(); - } - // We don't want to write out resolved bindings -- we want to write out the generic version. checkState( @@ -162,7 +147,9 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI dependency.key().type().xprocessing(), generatedTypeName.packageName()); String fieldName = fieldNames.getUniqueName(bindingField.name()); - TypeName fieldType = useRawFrameworkType ? bindingField.type().rawType : bindingField.type(); + TypeName fieldType = useRawFrameworkType + ? TypeNames.rawTypeName(bindingField.type()) + : bindingField.type(); FieldSpec.Builder fieldBuilder = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL); ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(fieldType, fieldName); diff --git a/java/dagger/internal/codegen/writing/OptionalFactories.java b/java/dagger/internal/codegen/writing/OptionalFactories.java index deb9dbfa8..3c327e8bc 100644 --- a/java/dagger/internal/codegen/writing/OptionalFactories.java +++ b/java/dagger/internal/codegen/writing/OptionalFactories.java @@ -28,8 +28,8 @@ import static dagger.internal.codegen.base.RequestKinds.requestTypeName; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.javapoet.TypeNames.abstractProducerOf; +import static dagger.internal.codegen.javapoet.TypeNames.daggerProviderOf; import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf; -import static dagger.internal.codegen.javapoet.TypeNames.providerOf; import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.ABSENT_OPTIONAL_FIELD; import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.ABSENT_OPTIONAL_METHOD; import static dagger.internal.codegen.writing.ComponentImplementation.TypeSpecKind.PRESENT_FACTORY; @@ -62,15 +62,12 @@ import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.AnnotationSpecs; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.RequestKind; -import dagger.producers.Producer; -import dagger.producers.internal.Producers; import java.util.Comparator; import java.util.Map; import java.util.Optional; import java.util.TreeMap; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Provider; /** The nested class and static methods required by the component to implement optional bindings. */ // TODO(dpb): Name members simply if a component uses only one of Guava or JDK Optional. @@ -150,15 +147,15 @@ final class OptionalFactories { "absent%sProvider", UPPER_UNDERSCORE.to(UPPER_CAMEL, optionalKind.name()))) .addModifiers(PRIVATE, STATIC) .addTypeVariable(typeVariable) - .returns(providerOf(optionalKind.of(typeVariable))) + .returns(daggerProviderOf(optionalKind.of(typeVariable))) .addJavadoc( "Returns a {@link $T} that returns {@code $L}.", - TypeNames.PROVIDER, + TypeNames.DAGGER_PROVIDER, optionalKind.absentValueExpression()) .addCode("$L // safe covariant cast\n", AnnotationSpecs.suppressWarnings(UNCHECKED)) .addStatement( "$1T provider = ($1T) $2N", - providerOf(optionalKind.of(typeVariable)), + daggerProviderOf(optionalKind.of(typeVariable)), perGeneratedFileCache.absentOptionalProviderFields.computeIfAbsent( optionalKind, kind -> { @@ -176,7 +173,7 @@ final class OptionalFactories { */ private FieldSpec absentOptionalProviderField(OptionalKind optionalKind) { return FieldSpec.builder( - TypeNames.PROVIDER, + TypeNames.DAGGER_PROVIDER, String.format("ABSENT_%s_PROVIDER", optionalKind.name()), PRIVATE, STATIC, @@ -185,7 +182,7 @@ final class OptionalFactories { .initializer("$T.create($L)", InstanceFactory.class, optionalKind.absentValueExpression()) .addJavadoc( "A {@link $T} that returns {@code $L}.", - TypeNames.PROVIDER, + TypeNames.DAGGER_PROVIDER, optionalKind.absentValueExpression()) .build(); } @@ -193,7 +190,7 @@ final class OptionalFactories { /** Information about the type of a factory for present bindings. */ @AutoValue abstract static class PresentFactorySpec { - /** Whether the factory is a {@link Provider} or a {@link Producer}. */ + /** Whether the factory is a {@code Provider} or a {@code Producer}. */ abstract FrameworkType frameworkType(); /** What kind of {@code Optional} is returned. */ @@ -302,7 +299,7 @@ final class OptionalFactories { * {@code Producer<Optional<Produced<T>>>}. * </ul> * - * @param delegateFactory an expression for a {@link Provider} or {@link Producer} of the + * @param delegateFactory an expression for a {@code Provider} or {@code Producer} of the * underlying type */ CodeBlock presentOptionalFactory(ContributionBinding binding, CodeBlock delegateFactory) { @@ -415,7 +412,9 @@ final class OptionalFactories { spec.optionalKind(), spec.valueType(), CodeBlock.of( - "$T.createFutureProduced($N.get())", Producers.class, delegateField))) + "$T.createFutureProduced($N.get())", + TypeNames.PRODUCERS, + delegateField))) .build(); default: diff --git a/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java b/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java index b77531456..8c9219197 100644 --- a/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java @@ -33,13 +33,13 @@ import dagger.internal.codegen.base.OptionalType.OptionalKind; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.model.DependencyRequest; +import dagger.internal.codegen.model.RequestKind; /** A binding expression for optional bindings. */ final class OptionalRequestRepresentation extends RequestRepresentation { private final ProvisionBinding binding; private final ComponentRequestRepresentations componentRequestRepresentations; private final XProcessingEnv processingEnv; - private final boolean isExperimentalMergedMode; @AssistedInject OptionalRequestRepresentation( @@ -50,8 +50,6 @@ final class OptionalRequestRepresentation extends RequestRepresentation { this.binding = binding; this.componentRequestRepresentations = componentRequestRepresentations; this.processingEnv = processingEnv; - this.isExperimentalMergedMode = - componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -78,18 +76,15 @@ final class OptionalRequestRepresentation extends RequestRepresentation { DependencyRequest dependency = getOnlyElement(binding.dependencies()); CodeBlock dependencyExpression = - isExperimentalMergedMode - ? componentRequestRepresentations - .getExperimentalSwitchingProviderDependencyRepresentation( - bindingRequest(dependency)) - .getDependencyExpression(dependency.kind(), binding) - .codeBlock() - : componentRequestRepresentations - .getDependencyExpression(bindingRequest(dependency), requestingClass) - .codeBlock(); + componentRequestRepresentations + .getDependencyExpression(bindingRequest(dependency), requestingClass) + .codeBlock(); - return isTypeAccessibleFrom( - dependency.key().type().xprocessing(), requestingClass.packageName()) + boolean needsObjectExpression = !isTypeAccessibleFrom( + dependency.key().type().xprocessing(), requestingClass.packageName()) + || (isPreJava8SourceVersion(processingEnv) && dependency.kind() == RequestKind.PROVIDER); + + return !needsObjectExpression ? Expression.create( binding.key().type().xprocessing(), optionalKind.presentExpression(dependencyExpression)) diff --git a/java/dagger/internal/codegen/writing/ProducerEntryPointView.java b/java/dagger/internal/codegen/writing/ProducerEntryPointView.java index 648e2537f..34d54584d 100644 --- a/java/dagger/internal/codegen/writing/ProducerEntryPointView.java +++ b/java/dagger/internal/codegen/writing/ProducerEntryPointView.java @@ -31,14 +31,11 @@ import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.RequestKind; import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; -import dagger.producers.Producer; -import dagger.producers.internal.CancellationListener; -import dagger.producers.internal.Producers; import java.util.Optional; /** - * A factory of {@linkplain Producers#entryPointViewOf(Producer, CancellationListener) entry point - * views} of {@link Producer}s. + * A factory of {@code Producers#entryPointViewOf(Producer, CancellationListener)} of + * {@code Producer}s. */ final class ProducerEntryPointView { private final ShardImplementation shardImplementation; @@ -50,9 +47,8 @@ final class ProducerEntryPointView { } /** - * Returns an expression for an {@linkplain Producers#entryPointViewOf(Producer, - * CancellationListener) entry point view} of a producer if the component method returns a {@link - * Producer} or {@link com.google.common.util.concurrent.ListenableFuture}. + * Returns an expression for an {@code Producers#entryPointViewOf(Producer, CancellationListener)} + * of a producer if the component method returns a {@code Producer} or {@code ListenableFuture}. * * <p>This is intended to be a replacement implementation for {@link * dagger.internal.codegen.writing.RequestRepresentation#getDependencyExpressionForComponentMethod(ComponentMethodDescriptor, @@ -97,7 +93,7 @@ final class ProducerEntryPointView { CodeBlock.of( "this.$N = $T.entryPointViewOf($L, $L);", field, - Producers.class, + TypeNames.PRODUCERS, producerExpression.getDependencyExpression(shardImplementation.name()).codeBlock(), // Always pass in the componentShard reference here rather than the owning shard for // this key because this needs to be the root CancellationListener. diff --git a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java index e6c88a9c7..1d8ab02f7 100644 --- a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java @@ -77,16 +77,13 @@ import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.DependencyRequest; import dagger.internal.codegen.model.Key; import dagger.internal.codegen.model.RequestKind; -import dagger.producers.Producer; -import dagger.producers.internal.AbstractProducesMethodProducer; -import dagger.producers.internal.Producers; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import javax.inject.Inject; -/** Generates {@link Producer} implementations from {@link ProductionBinding} instances. */ +/** Generates {@code Producer} implementations from {@link ProductionBinding} instances. */ public final class ProducerFactoryGenerator extends SourceFileGenerator<ProductionBinding> { private final CompilerOptions compilerOptions; private final KeyFactory keyFactory; @@ -163,7 +160,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti addFieldAndConstructorParameter( factoryBuilder, constructorBuilder, fieldName, bindingField.type()); fieldsBuilder.put(dependency, field); - frameworkFieldAssignments.add(fieldAssignment(field, bindingField.type())); + frameworkFieldAssignments.add(fieldAssignment(field, bindingField)); } } ImmutableMap<DependencyRequest, FieldSpec> fields = fieldsBuilder.build(); @@ -219,7 +216,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti factoryBuilder .superclass( ParameterizedTypeName.get( - ClassName.get(AbstractProducesMethodProducer.class), + TypeNames.ABSTRACT_PRODUCES_METHOD_PRODUCER, futureTransform.applyArgType(), providedTypeName)) .addMethod(constructor) @@ -260,11 +257,12 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti return field; } - private static CodeBlock fieldAssignment(FieldSpec field, ParameterizedTypeName type) { + private static CodeBlock fieldAssignment(FieldSpec field, FrameworkField frameworkField) { CodeBlock.Builder statement = CodeBlock.builder(); - if (type != null && type.rawType.equals(TypeNames.PRODUCER)) { + if (frameworkField.type() != null + && TypeNames.rawTypeName(frameworkField.type()).equals(TypeNames.PRODUCER)) { statement.addStatement( - "this.$1N = $2T.nonCancellationPropagatingViewOf($1N)", field, Producers.class); + "this.$1N = $2T.nonCancellationPropagatingViewOf($1N)", field, TypeNames.PRODUCERS); } else { statement.addStatement("this.$1N = $1N", field); } @@ -275,7 +273,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti MethodSpec.Builder constructorBuilder, FieldSpec field, ParameterizedTypeName type) { if (type != null && type.rawType.equals(TypeNames.PRODUCER)) { constructorBuilder.addStatement( - "this.$1N = $2T.nonCancellationPropagatingViewOf($1N)", field, Producers.class); + "this.$1N = $2T.nonCancellationPropagatingViewOf($1N)", field, TypeNames.PRODUCERS); } else { constructorBuilder.addStatement("this.$1N = $1N", field); } diff --git a/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java b/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java index 05dd50b27..9d0895673 100644 --- a/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java @@ -26,10 +26,9 @@ import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.RequestKind; import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression; -import dagger.producers.Producer; import java.util.Optional; -/** An {@link Producer} creation expression for provision bindings. */ +/** An {@code Producer} creation expression for provision bindings. */ final class ProducerFromProviderCreationExpression implements FrameworkInstanceCreationExpression { private final RequestRepresentation providerRequestRepresentation; private final ClassName requestingClass; diff --git a/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java b/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java index 813010cfd..cdcc38933 100644 --- a/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java @@ -26,9 +26,9 @@ import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescri import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; import dagger.internal.codegen.javapoet.Expression; +import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.model.Key; import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; -import dagger.producers.internal.Producers; /** Binding expression for producer node instances. */ final class ProducerNodeInstanceRequestRepresentation @@ -61,7 +61,7 @@ final class ProducerNodeInstanceRequestRepresentation key, CodeBlock.of( "$T.cancel($L, $N);", - Producers.class, + TypeNames.PRODUCERS, result.codeBlock(), ComponentImplementation.MAY_INTERRUPT_IF_RUNNING_PARAM)); return result; diff --git a/java/dagger/internal/codegen/writing/ProviderInstanceRequestRepresentation.java b/java/dagger/internal/codegen/writing/ProviderInstanceRequestRepresentation.java index 9bd8f3d8d..f031a72a6 100644 --- a/java/dagger/internal/codegen/writing/ProviderInstanceRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/ProviderInstanceRequestRepresentation.java @@ -20,18 +20,29 @@ import androidx.room.compiler.processing.XProcessingEnv; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; -import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.binding.FrameworkType; +import dagger.internal.codegen.binding.ProvisionBinding; /** Binding expression for provider instances. */ final class ProviderInstanceRequestRepresentation extends FrameworkInstanceRequestRepresentation { @AssistedInject ProviderInstanceRequestRepresentation( - @Assisted ContributionBinding binding, - @Assisted FrameworkInstanceSupplier frameworkInstanceSupplier, + @Assisted ProvisionBinding binding, + SwitchingProviderInstanceSupplier.Factory switchingProviderInstanceSupplierFactory, + StaticFactoryInstanceSupplier.Factory staticFactoryInstanceSupplierFactory, + ProviderInstanceSupplier.Factory providerInstanceSupplierFactory, + ComponentImplementation componentImplementation, XProcessingEnv processingEnv) { - super(binding, frameworkInstanceSupplier, processingEnv); + super( + binding, + frameworkInstanceSupplier( + binding, + switchingProviderInstanceSupplierFactory, + staticFactoryInstanceSupplierFactory, + providerInstanceSupplierFactory, + componentImplementation), + processingEnv); } @Override @@ -39,9 +50,27 @@ final class ProviderInstanceRequestRepresentation extends FrameworkInstanceReque return FrameworkType.PROVIDER; } + private static FrameworkInstanceSupplier frameworkInstanceSupplier( + ProvisionBinding binding, + SwitchingProviderInstanceSupplier.Factory switchingProviderInstanceSupplierFactory, + StaticFactoryInstanceSupplier.Factory staticFactoryInstanceSupplierFactory, + ProviderInstanceSupplier.Factory providerInstanceSupplierFactory, + ComponentImplementation componentImplementation) { + FrameworkInstanceKind frameworkInstanceKind = + FrameworkInstanceKind.from(binding, componentImplementation.compilerMode()); + switch (frameworkInstanceKind) { + case SWITCHING_PROVIDER: + return switchingProviderInstanceSupplierFactory.create(binding); + case STATIC_FACTORY: + return staticFactoryInstanceSupplierFactory.create(binding); + case PROVIDER_FIELD: + return providerInstanceSupplierFactory.create(binding); + } + throw new AssertionError("Unexpected FrameworkInstanceKind: " + frameworkInstanceKind); + } + @AssistedFactory static interface Factory { - ProviderInstanceRequestRepresentation create( - ContributionBinding binding, FrameworkInstanceSupplier frameworkInstanceSupplier); + ProviderInstanceRequestRepresentation create(ProvisionBinding binding); } } diff --git a/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java b/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java index 4ae7f3759..0f9b6f3dd 100644 --- a/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java +++ b/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java @@ -25,7 +25,6 @@ import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.BindingRequest; import dagger.internal.codegen.binding.ProvisionBinding; -import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.model.RequestKind; import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode; @@ -43,34 +42,17 @@ final class ProvisionBindingRepresentation implements BindingRepresentation { @AssistedInject ProvisionBindingRepresentation( @Assisted ProvisionBinding binding, - BindingGraph graph, - ComponentImplementation componentImplementation, DirectInstanceBindingRepresentation.Factory directInstanceBindingRepresentationFactory, FrameworkInstanceBindingRepresentation.Factory frameworkInstanceBindingRepresentationFactory, - SwitchingProviderInstanceSupplier.Factory switchingProviderInstanceSupplierFactory, - ProviderInstanceSupplier.Factory providerInstanceSupplierFactory, - StaticFactoryInstanceSupplier.Factory staticFactoryInstanceSupplierFactory, - CompilerOptions compilerOptions) { + BindingGraph graph, + ComponentImplementation componentImplementation) { this.binding = binding; this.graph = graph; this.compilerMode = componentImplementation.compilerMode(); this.directInstanceBindingRepresentation = directInstanceBindingRepresentationFactory.create(binding); - FrameworkInstanceSupplier frameworkInstanceSupplier = null; - switch (FrameworkInstanceKind.from(binding, compilerMode)) { - case SWITCHING_PROVIDER: - case EXPERIMENTAL_SWITCHING_PROVIDER: - frameworkInstanceSupplier = switchingProviderInstanceSupplierFactory.create(binding); - break; - case STATIC_FACTORY: - frameworkInstanceSupplier = staticFactoryInstanceSupplierFactory.create(binding); - break; - case PROVIDER_FIELD: - frameworkInstanceSupplier = providerInstanceSupplierFactory.create(binding); - break; - } this.frameworkInstanceBindingRepresentation = - frameworkInstanceBindingRepresentationFactory.create(binding, frameworkInstanceSupplier); + frameworkInstanceBindingRepresentationFactory.create(binding); } @Override @@ -81,9 +63,6 @@ final class ProvisionBindingRepresentation implements BindingRepresentation { } private boolean usesDirectInstanceExpression(RequestKind requestKind) { - if (compilerMode.isExperimentalMergedMode()) { - return false; - } if (requestKind != RequestKind.INSTANCE && requestKind != RequestKind.FUTURE) { return false; } diff --git a/java/dagger/internal/codegen/writing/SetRequestRepresentation.java b/java/dagger/internal/codegen/writing/SetRequestRepresentation.java index f5d77ad48..68a340a1b 100644 --- a/java/dagger/internal/codegen/writing/SetRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/SetRequestRepresentation.java @@ -47,7 +47,6 @@ final class SetRequestRepresentation extends RequestRepresentation { private final BindingGraph graph; private final ComponentRequestRepresentations componentRequestRepresentations; private final XProcessingEnv processingEnv; - private final boolean isExperimentalMergedMode; @AssistedInject SetRequestRepresentation( @@ -60,8 +59,6 @@ final class SetRequestRepresentation extends RequestRepresentation { this.graph = graph; this.componentRequestRepresentations = componentRequestRepresentations; this.processingEnv = processingEnv; - this.isExperimentalMergedMode = - componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -139,14 +136,7 @@ final class SetRequestRepresentation extends RequestRepresentation { DependencyRequest dependency, ClassName requestingClass) { RequestRepresentation bindingExpression = componentRequestRepresentations.getRequestRepresentation(bindingRequest(dependency)); - CodeBlock expression = - isExperimentalMergedMode - ? componentRequestRepresentations - .getExperimentalSwitchingProviderDependencyRepresentation( - bindingRequest(dependency)) - .getDependencyExpression(dependency.kind(), binding) - .codeBlock() - : bindingExpression.getDependencyExpression(requestingClass).codeBlock(); + CodeBlock expression = bindingExpression.getDependencyExpression(requestingClass).codeBlock(); // TODO(b/211774331): Type casting should be Set after contributions to Set multibinding are // limited to be Set. diff --git a/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java b/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java index 4026f1681..79613fd40 100644 --- a/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java @@ -19,7 +19,6 @@ package dagger.internal.codegen.writing; import static androidx.room.compiler.processing.XElementKt.isConstructor; import static androidx.room.compiler.processing.XElementKt.isMethod; import static com.google.common.base.Preconditions.checkArgument; -import static dagger.internal.codegen.binding.BindingRequest.bindingRequest; import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; @@ -58,7 +57,6 @@ final class SimpleMethodRequestRepresentation extends RequestRepresentation { private final MembersInjectionMethods membersInjectionMethods; private final ComponentRequirementExpressions componentRequirementExpressions; private final ShardImplementation shardImplementation; - private final boolean isExperimentalMergedMode; @AssistedInject SimpleMethodRequestRepresentation( @@ -80,8 +78,6 @@ final class SimpleMethodRequestRepresentation extends RequestRepresentation { this.membersInjectionMethods = membersInjectionMethods; this.componentRequirementExpressions = componentRequirementExpressions; this.shardImplementation = componentImplementation.shardImplementation(binding); - this.isExperimentalMergedMode = - componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -146,12 +142,8 @@ final class SimpleMethodRequestRepresentation extends RequestRepresentation { } private Expression dependencyArgument(DependencyRequest dependency, ClassName requestingClass) { - return isExperimentalMergedMode - ? componentRequestRepresentations - .getExperimentalSwitchingProviderDependencyRepresentation(bindingRequest(dependency)) - .getDependencyExpression(dependency.kind(), provisionBinding) - : componentRequestRepresentations.getDependencyArgumentExpression( - dependency, requestingClass); + return componentRequestRepresentations.getDependencyArgumentExpression( + dependency, requestingClass); } private Expression injectMembers(CodeBlock instance, ClassName requestingClass) { @@ -167,11 +159,8 @@ final class SimpleMethodRequestRepresentation extends RequestRepresentation { instance = CodeBlock.of("($T) ($T) $L", keyType, rawTypeName(keyType), instance); } } - return isExperimentalMergedMode - ? membersInjectionMethods.getInjectExpressionExperimental( - provisionBinding, instance, requestingClass) - : membersInjectionMethods.getInjectExpression( - provisionBinding.key(), instance, requestingClass); + return membersInjectionMethods.getInjectExpression( + provisionBinding.key(), instance, requestingClass); } private Optional<CodeBlock> moduleReference(ClassName requestingClass) { @@ -180,11 +169,7 @@ final class SimpleMethodRequestRepresentation extends RequestRepresentation { .contributingModule() .map(XTypeElement::getType) .map(ComponentRequirement::forModule) - .map( - module -> - isExperimentalMergedMode - ? CodeBlock.of("(($T) dependencies[0])", module.type().getTypeName()) - : componentRequirementExpressions.getExpression(module, requestingClass)) + .map(module -> componentRequirementExpressions.getExpression(module, requestingClass)) : Optional.empty(); } diff --git a/java/dagger/internal/codegen/writing/StaticMemberSelects.java b/java/dagger/internal/codegen/writing/StaticMemberSelects.java index 3fea6173f..a1ea63c03 100644 --- a/java/dagger/internal/codegen/writing/StaticMemberSelects.java +++ b/java/dagger/internal/codegen/writing/StaticMemberSelects.java @@ -22,11 +22,11 @@ import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementType import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.binding.SourceFiles.setFactoryClassName; import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; +import static dagger.internal.codegen.javapoet.TypeNames.DAGGER_PROVIDER; import static dagger.internal.codegen.javapoet.TypeNames.FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.MAP_FACTORY; import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER; import static dagger.internal.codegen.javapoet.TypeNames.PRODUCERS; -import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; @@ -52,7 +52,7 @@ final class StaticMemberSelects { ? new ParameterizedStaticMethod( PRODUCERS, typeParameters, CodeBlock.of("emptyMapProducer()"), PRODUCER) : new ParameterizedStaticMethod( - MAP_FACTORY, typeParameters, CodeBlock.of("emptyMapProvider()"), PROVIDER); + MAP_FACTORY, typeParameters, CodeBlock.of("emptyMapProvider()"), DAGGER_PROVIDER); } /** diff --git a/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java b/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java index eeadabee9..c71c70af6 100644 --- a/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java +++ b/java/dagger/internal/codegen/writing/SubcomponentCreatorRequestRepresentation.java @@ -20,29 +20,23 @@ import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.FieldSpec; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; import dagger.internal.codegen.binding.ContributionBinding; import dagger.internal.codegen.javapoet.Expression; import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; -import java.util.ArrayList; -import java.util.List; /** A binding expression for a subcomponent creator that just invokes the constructor. */ final class SubcomponentCreatorRequestRepresentation extends RequestRepresentation { private final ShardImplementation shardImplementation; private final ContributionBinding binding; - private final boolean isExperimentalMergedMode; @AssistedInject SubcomponentCreatorRequestRepresentation( @Assisted ContributionBinding binding, ComponentImplementation componentImplementation) { this.binding = binding; this.shardImplementation = componentImplementation.shardImplementation(binding); - this.isExperimentalMergedMode = - componentImplementation.compilerMode().isExperimentalMergedMode(); } @Override @@ -51,20 +45,9 @@ final class SubcomponentCreatorRequestRepresentation extends RequestRepresentati binding.key().type().xprocessing(), "new $T($L)", shardImplementation.getSubcomponentCreatorSimpleName(binding.key()), - isExperimentalMergedMode - ? getDependenciesExperimental() - : shardImplementation.componentFieldsByImplementation().values().stream() - .map(field -> CodeBlock.of("$N", field)) - .collect(toParametersCodeBlock())); - } - - private CodeBlock getDependenciesExperimental() { - List<CodeBlock> expressions = new ArrayList<>(); - int index = 0; - for (FieldSpec field : shardImplementation.componentFieldsByImplementation().values()) { - expressions.add(CodeBlock.of("($T) dependencies[$L]", field.type, index++)); - } - return expressions.stream().collect(toParametersCodeBlock()); + shardImplementation.componentFieldsByImplementation().values().stream() + .map(field -> CodeBlock.of("$N", field)) + .collect(toParametersCodeBlock())); } CodeBlock getDependencyExpressionArguments() { diff --git a/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java b/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java index 7bea206f6..6f8ca7b3f 100644 --- a/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java +++ b/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java @@ -46,11 +46,9 @@ final class SwitchingProviderInstanceSupplier implements FrameworkInstanceSuppli unscopedDirectInstanceRequestRepresentationFactory) { ShardImplementation shardImplementation = componentImplementation.shardImplementation(binding); FrameworkInstanceCreationExpression frameworkInstanceCreationExpression = - componentImplementation.compilerMode().isExperimentalMergedMode() - ? shardImplementation.getExperimentalSwitchingProviders() - .newFrameworkInstanceCreationExpression( - binding, unscopedDirectInstanceRequestRepresentationFactory.create(binding)) - : shardImplementation.getSwitchingProviders().newFrameworkInstanceCreationExpression( + shardImplementation + .getSwitchingProviders() + .newFrameworkInstanceCreationExpression( binding, unscopedDirectInstanceRequestRepresentationFactory.create(binding)); this.frameworkInstanceSupplier = new FrameworkFieldInitializer( diff --git a/java/dagger/internal/codegen/writing/SwitchingProviders.java b/java/dagger/internal/codegen/writing/SwitchingProviders.java index e6c3bbf52..736f51529 100644 --- a/java/dagger/internal/codegen/writing/SwitchingProviders.java +++ b/java/dagger/internal/codegen/writing/SwitchingProviders.java @@ -24,7 +24,7 @@ import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; -import static dagger.internal.codegen.javapoet.TypeNames.providerOf; +import static dagger.internal.codegen.javapoet.TypeNames.daggerProviderOf; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; @@ -172,7 +172,7 @@ final class SwitchingProviders { classBuilder(switchingProviderType) .addModifiers(PRIVATE, FINAL, STATIC) .addTypeVariable(T) - .addSuperinterface(providerOf(T)) + .addSuperinterface(daggerProviderOf(T)) .addMethods(getMethods()); // The SwitchingProvider constructor lists all component parameters first and switch id last. diff --git a/java/dagger/internal/codegen/xprocessing/BUILD b/java/dagger/internal/codegen/xprocessing/BUILD index 262be175a..da465e1d0 100644 --- a/java/dagger/internal/codegen/xprocessing/BUILD +++ b/java/dagger/internal/codegen/xprocessing/BUILD @@ -30,6 +30,7 @@ java_library( deps = [ ":xprocessing-lib", "//java/dagger/internal/codegen/extension", + "//java/dagger/spi", "//third_party/java/auto:common", "//third_party/java/guava/base", "//third_party/java/guava/collect", diff --git a/java/dagger/internal/codegen/xprocessing/DaggerElements.java b/java/dagger/internal/codegen/xprocessing/DaggerElements.java new file mode 100644 index 000000000..84daeb0a9 --- /dev/null +++ b/java/dagger/internal/codegen/xprocessing/DaggerElements.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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.xprocessing; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.compat.XConverters; +import com.google.devtools.ksp.symbol.KSClassDeclaration; +import com.google.devtools.ksp.symbol.KSFunctionDeclaration; +import com.google.devtools.ksp.symbol.KSPropertyDeclaration; +import com.google.devtools.ksp.symbol.KSValueParameter; +import dagger.spi.model.DaggerElement; +import dagger.spi.model.DaggerProcessingEnv; +import dagger.spi.model.DaggerType; + +/** Convert Dagger model types to XProcessing types. */ +public final class DaggerElements { + public static XElement toXProcessing( + DaggerElement element, DaggerProcessingEnv daggerProcessingEnv) { + XProcessingEnv processingEnv = toXProcessing(daggerProcessingEnv); + switch (element.backend()) { + case JAVAC: + return XConverters.toXProcessing(element.javac(), processingEnv); + case KSP: + if (element.ksp() instanceof KSFunctionDeclaration) { + return XConverters.toXProcessing((KSFunctionDeclaration) element.ksp(), processingEnv); + } else if (element.ksp() instanceof KSClassDeclaration) { + return XConverters.toXProcessing((KSClassDeclaration) element.ksp(), processingEnv); + } else if (element.ksp() instanceof KSValueParameter) { + return XConverters.toXProcessing((KSValueParameter) element.ksp(), processingEnv); + } else if (element.ksp() instanceof KSPropertyDeclaration) { + return XConverters.toXProcessing((KSPropertyDeclaration) element.ksp(), processingEnv); + } + throw new IllegalStateException( + String.format("Unsupported ksp declaration %s.", element.ksp())); + } + throw new IllegalStateException( + String.format("Backend %s not supported yet.", element.backend())); + } + + public static XType toXProcessing(DaggerType type, DaggerProcessingEnv daggerProcessingEnv) { + XProcessingEnv processingEnv = toXProcessing(daggerProcessingEnv); + switch (type.backend()) { + case JAVAC: + return XConverters.toXProcessing(type.javac(), processingEnv); + case KSP: + return XConverters.toXProcessing(type.ksp(), processingEnv); + } + throw new IllegalStateException(String.format("Backend %s not supported yet.", type.backend())); + } + + public static XProcessingEnv toXProcessing(DaggerProcessingEnv processingEnv) { + switch (processingEnv.backend()) { + case JAVAC: + return XProcessingEnv.create(processingEnv.javac()); + case KSP: + return XProcessingEnv.create(processingEnv.ksp(), processingEnv.resolver()); + } + throw new IllegalStateException( + String.format("Backend %s not supported yet.", processingEnv.backend())); + } + + private DaggerElements() {} +} diff --git a/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java b/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java index b28d9c008..bf8e94f2a 100644 --- a/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java +++ b/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java @@ -50,8 +50,7 @@ public final class JavaPoetExt { } public static ParameterSpec toParameterSpec(XExecutableParameterElement param) { - return ParameterSpec.builder(param.getType().getTypeName(), XElements.getSimpleName(param)) - .build(); + return ParameterSpec.builder(param.getType().getTypeName(), param.getJvmName()).build(); } private JavaPoetExt() {} diff --git a/java/dagger/internal/codegen/xprocessing/MethodSpecs.java b/java/dagger/internal/codegen/xprocessing/MethodSpecs.java index fa1f26541..cc8c2f459 100644 --- a/java/dagger/internal/codegen/xprocessing/MethodSpecs.java +++ b/java/dagger/internal/codegen/xprocessing/MethodSpecs.java @@ -16,7 +16,6 @@ package dagger.internal.codegen.xprocessing; -import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static javax.lang.model.element.Modifier.PROTECTED; import static javax.lang.model.element.Modifier.PUBLIC; @@ -47,7 +46,7 @@ public final class MethodSpecs { builder.addModifiers(PROTECTED); } for (int i = 0; i < methodType.getParameterTypes().size(); i++) { - String parameterName = getSimpleName(method.getParameters().get(i)); + String parameterName = method.getParameters().get(i).getJvmName(); TypeName parameterType = methodType.getParameterTypes().get(i).getTypeName(); builder.addParameter(ParameterSpec.builder(parameterType, parameterName).build()); } diff --git a/java/dagger/internal/codegen/xprocessing/XElements.java b/java/dagger/internal/codegen/xprocessing/XElements.java index 552be65f9..b63d4d93d 100644 --- a/java/dagger/internal/codegen/xprocessing/XElements.java +++ b/java/dagger/internal/codegen/xprocessing/XElements.java @@ -47,6 +47,7 @@ import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.XTypeParameterElement; import androidx.room.compiler.processing.XVariableElement; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.devtools.ksp.symbol.KSAnnotated; import com.squareup.javapoet.ClassName; @@ -54,6 +55,7 @@ import java.util.Collection; import java.util.Optional; import javax.annotation.Nullable; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; // TODO(bcorso): Consider moving these methods into XProcessing library. /** A utility class for {@link XElement} helper methods. */ @@ -383,5 +385,37 @@ public final class XElements { return element.getClosestMemberContainer().asClassName().getPackageName(); } + public static boolean isFinal(XExecutableElement element) { + if (element.isFinal()) { + return true; + } + if (getProcessingEnv(element).getBackend() == XProcessingEnv.Backend.KSP) { + if (toKS(element).getModifiers().contains(com.google.devtools.ksp.symbol.Modifier.FINAL)) { + return true; + } + } + return false; + } + + public static ImmutableList<Modifier> getModifiers(XExecutableElement element) { + ImmutableList.Builder<Modifier> builder = ImmutableList.builder(); + if (isFinal(element)) { + builder.add(Modifier.FINAL); + } else if (element.isAbstract()) { + builder.add(Modifier.ABSTRACT); + } + if (element.isStatic()) { + builder.add(Modifier.STATIC); + } + if (element.isPublic()) { + builder.add(Modifier.PUBLIC); + } else if (element.isPrivate()) { + builder.add(Modifier.PRIVATE); + } else if (element.isProtected()) { + builder.add(Modifier.PROTECTED); + } + return builder.build(); + } + private XElements() {} } diff --git a/java/dagger/internal/codegen/xprocessing/XMethodElements.java b/java/dagger/internal/codegen/xprocessing/XMethodElements.java index 3066d797e..cbff642c3 100644 --- a/java/dagger/internal/codegen/xprocessing/XMethodElements.java +++ b/java/dagger/internal/codegen/xprocessing/XMethodElements.java @@ -16,11 +16,8 @@ package dagger.internal.codegen.xprocessing; -import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; -import static androidx.room.compiler.processing.compat.XConverters.toJavac; import androidx.room.compiler.processing.XMethodElement; -import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; // TODO(bcorso): Consider moving these methods into XProcessing library. @@ -40,19 +37,5 @@ public final class XMethodElements { return !method.getExecutableType().getTypeVariableNames().isEmpty(); } - // TODO(b/278628663): Replace with XMethodElement#isDefault. - public static boolean isDefault(XMethodElement method) { - XProcessingEnv processingEnv = getProcessingEnv(method); - switch (processingEnv.getBackend()) { - case JAVAC: - return toJavac(method).isDefault(); - case KSP: - throw new AssertionError( - "XMethodElement#isDefault() is not supported on KSP yet: " - + XElements.toStableString(method)); - } - throw new AssertionError(String.format("Unsupported backend %s", processingEnv.getBackend())); - } - private XMethodElements() {} } diff --git a/java/dagger/internal/codegen/xprocessing/XTypeElements.java b/java/dagger/internal/codegen/xprocessing/XTypeElements.java index 6d9c56486..7330b8f6a 100644 --- a/java/dagger/internal/codegen/xprocessing/XTypeElements.java +++ b/java/dagger/internal/codegen/xprocessing/XTypeElements.java @@ -16,21 +16,16 @@ package dagger.internal.codegen.xprocessing; -import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static kotlin.streams.jdk8.StreamsKt.asStream; import androidx.room.compiler.processing.XHasModifiers; import androidx.room.compiler.processing.XMethodElement; -import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XTypeElement; import androidx.room.compiler.processing.XTypeParameterElement; -import androidx.room.compiler.processing.compat.XConverters; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.devtools.ksp.symbol.Origin; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeVariableName; // TODO(bcorso): Consider moving these methods into XProcessing library. @@ -142,20 +137,5 @@ public final class XTypeElements { return visibilities.build(); } - /** Returns true if the source of the given type element is Kotlin. */ - public static boolean isKotlinSource(XTypeElement typeElement) { - XProcessingEnv processingEnv = getProcessingEnv(typeElement); - switch (processingEnv.getBackend()) { - case KSP: - // If this is KSP, then we should be able to check the origin of the declaration. - Origin origin = XConverters.toKS(typeElement).getOrigin(); - return origin == Origin.KOTLIN || origin == Origin.KOTLIN_LIB; - case JAVAC: - // If this is KAPT, then the java stubs should have kotlin metadata. - return typeElement.hasAnnotation(ClassName.get("kotlin", "Metadata")); - } - throw new AssertionError("Unhandled backend kind: " + processingEnv.getBackend()); - } - private XTypeElements() {} } diff --git a/java/dagger/internal/codegen/xprocessing/XTypes.java b/java/dagger/internal/codegen/xprocessing/XTypes.java index 654ce697a..7f4b77aac 100644 --- a/java/dagger/internal/codegen/xprocessing/XTypes.java +++ b/java/dagger/internal/codegen/xprocessing/XTypes.java @@ -388,12 +388,46 @@ public final class XTypes { * {@link Optional} is returned if there is no non-{@link Object} superclass. */ public static Optional<XType> nonObjectSuperclass(XType type) { - return isDeclared(type) - ? type.getSuperTypes().stream() - .filter(supertype -> !supertype.getTypeName().equals(TypeName.OBJECT)) - .filter(supertype -> isDeclared(supertype) && supertype.getTypeElement().isClass()) - .collect(toOptional()) - : Optional.empty(); + if (!isDeclared(type)) { + return Optional.empty(); + } + // We compare elements (rather than TypeName) here because its more efficient on the heap. + XTypeElement objectElement = objectElement(getProcessingEnv(type)); + XTypeElement typeElement = type.getTypeElement(); + if (!typeElement.isClass() || typeElement.equals(objectElement)) { + return Optional.empty(); + } + XType superClass = typeElement.getSuperClass(); + if (!isDeclared(superClass)) { + return Optional.empty(); + } + XTypeElement superClassElement = superClass.getTypeElement(); + if (!superClassElement.isClass() || superClassElement.equals(objectElement)) { + return Optional.empty(); + } + // TODO(b/310954522): XType#getSuperTypes() is less efficient (especially on the heap) as it + // requires creating XType for not just superclass but all super interfaces as well, so we go + // through a bit of effort here to avoid that call unless its absolutely necessary since + // nonObjectSuperclass is called quite a bit via InjectionSiteFactory. However, we should + // eventually optimize this on the XProcessing side instead, e.g. maybe separating + // XType#getSuperClass() into a separate method. + return superClass.getTypeArguments().isEmpty() + ? Optional.of(superClass) + : type.getSuperTypes().stream() + .filter(XTypes::isDeclared) + .filter(supertype -> supertype.getTypeElement().isClass()) + .filter(supertype -> !supertype.getTypeElement().equals(objectElement)) + .collect(toOptional()); + } + + private static XTypeElement objectElement(XProcessingEnv processingEnv) { + switch (processingEnv.getBackend()) { + case JAVAC: + return processingEnv.requireTypeElement(TypeName.OBJECT); + case KSP: + return processingEnv.requireTypeElement("kotlin.Any"); + } + throw new AssertionError("Unexpected backend: " + processingEnv.getBackend()); } /** diff --git a/java/dagger/internal/codegen/xprocessing/xprocessing-testing.jar b/java/dagger/internal/codegen/xprocessing/xprocessing-testing.jar Binary files differindex 7811751e0..4d0d7fd00 100644 --- a/java/dagger/internal/codegen/xprocessing/xprocessing-testing.jar +++ b/java/dagger/internal/codegen/xprocessing/xprocessing-testing.jar diff --git a/java/dagger/internal/codegen/xprocessing/xprocessing.jar b/java/dagger/internal/codegen/xprocessing/xprocessing.jar Binary files differindex 1c41e2053..24590f84d 100644 --- a/java/dagger/internal/codegen/xprocessing/xprocessing.jar +++ b/java/dagger/internal/codegen/xprocessing/xprocessing.jar diff --git a/java/dagger/lint/DaggerKotlinIssueDetector.kt b/java/dagger/lint/DaggerKotlinIssueDetector.kt index 8ca862309..6a4a33ca6 100644 --- a/java/dagger/lint/DaggerKotlinIssueDetector.kt +++ b/java/dagger/lint/DaggerKotlinIssueDetector.kt @@ -204,7 +204,8 @@ class DaggerKotlinIssueDetector : Detector(), SourceCodeScanner { ) { val containingClass = node.containingClass?.toUElement(UClass::class.java) ?: return if (containingClass.isObject()) { - val annotation = node.findAnnotation(JVM_STATIC_ANNOTATION)!! + val annotation = node.findAnnotation(JVM_STATIC_ANNOTATION) + ?: node.javaPsi.modifierList.findAnnotation(JVM_STATIC_ANNOTATION)!! context.report( ISSUE_JVM_STATIC_PROVIDES_IN_OBJECT, context.getLocation(annotation), diff --git a/java/dagger/model/BUILD b/java/dagger/model/BUILD index aaa830601..1a672dc03 100644 --- a/java/dagger/model/BUILD +++ b/java/dagger/model/BUILD @@ -44,7 +44,6 @@ java_library( deps = [ "//java/dagger:core", "//java/dagger/internal/codegen/extension", - "//java/dagger/producers", "//third_party/java/auto:common", "//third_party/java/auto:value", "//third_party/java/error_prone:annotations", diff --git a/java/dagger/model/BindingGraph.java b/java/dagger/model/BindingGraph.java index 1ccba4326..d09bf02c1 100644 --- a/java/dagger/model/BindingGraph.java +++ b/java/dagger/model/BindingGraph.java @@ -43,14 +43,13 @@ import javax.lang.model.element.TypeElement; * <p>A {@link BindingGraph} represents one of the following: * * <ul> - * <li>an entire component hierarchy rooted at a {@link dagger.Component} or {@link - * dagger.producers.ProductionComponent} - * <li>a partial component hierarchy rooted at a {@link dagger.Subcomponent} or {@link - * dagger.producers.ProductionSubcomponent} (only when the value of {@code - * -Adagger.fullBindingGraphValidation} is not {@code NONE}) - * <li>the bindings installed by a {@link Module} or {@link dagger.producers.ProducerModule}, - * including all subcomponents generated by {@link Module#subcomponents()} ()} and {@link - * dagger.producers.ProducerModule#subcomponents()} ()} + * <li>an entire component hierarchy rooted at a {@code Component} or {@code ProductionComponent} + * <li>a partial component hierarchy rooted at a {@code Subcomponent} or + * {@code ProductionSubcomponent} (only when the value of + * {@code -Adagger.fullBindingGraphValidation} is not {@code NONE}) + * <li>the bindings installed by a {@code Module} or {@code ProducerModule}, + * including all subcomponents generated by {@code Module#subcomponents()} and + * {@code ProducerModule#subcomponents()} * </ul> * * In the case of a {@link BindingGraph} representing a module, the root {@link ComponentNode} will diff --git a/java/dagger/model/BindingKind.java b/java/dagger/model/BindingKind.java index 1bfb08098..46d11309a 100644 --- a/java/dagger/model/BindingKind.java +++ b/java/dagger/model/BindingKind.java @@ -34,8 +34,7 @@ public enum BindingKind { ASSISTED_FACTORY, /** - * An implicit binding for a {@link dagger.Component}- or {@link - * dagger.producers.ProductionComponent}-annotated type. + * An implicit binding for a {@code Component}- or {@code ProductionComponent}-annotated type. */ COMPONENT, @@ -65,14 +64,13 @@ public enum BindingKind { /** A binding for a {@link dagger.BindsInstance}-annotated builder method. */ BOUND_INSTANCE, - /** A binding for a {@link dagger.producers.Produces}-annotated method. */ + /** A binding for a {@code Produces}-annotated method. */ PRODUCTION, /** - * A binding for a production method on a production component's {@linkplain - * dagger.producers.ProductionComponent#dependencies()} dependency} that returns a {@link - * com.google.common.util.concurrent.ListenableFuture} or {@link - * com.google.common.util.concurrent.FluentFuture}. Methods on production component dependencies + * A binding for a production method on a production component's + * {@code ProductionComponent#dependencies()} that returns a {@code ListenableFuture} or + * {@code FluentFuture}. Methods on production component dependencies * that don't return a future are considered {@linkplain #COMPONENT_PROVISION component provision * bindings}. */ diff --git a/java/dagger/model/ComponentPath.java b/java/dagger/model/ComponentPath.java index 5a74c7a51..9dfdbf93e 100644 --- a/java/dagger/model/ComponentPath.java +++ b/java/dagger/model/ComponentPath.java @@ -40,8 +40,7 @@ public abstract class ComponentPath { public abstract ImmutableList<TypeElement> components(); /** - * Returns the root {@link dagger.Component}- or {@link - * dagger.producers.ProductionComponent}-annotated type + * Returns the root {@code Component}- or {@code ProductionComponent}-annotated type */ public final TypeElement rootComponent() { return components().get(0); diff --git a/java/dagger/model/RequestKind.java b/java/dagger/model/RequestKind.java index 74a434633..6a19b0a08 100644 --- a/java/dagger/model/RequestKind.java +++ b/java/dagger/model/RequestKind.java @@ -19,11 +19,6 @@ package dagger.model; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; -import dagger.Lazy; -import dagger.producers.Produced; -import dagger.producers.Producer; -import javax.inject.Provider; - /** * Represents the different kinds of {@link javax.lang.model.type.TypeMirror types} that may be * requested as dependencies for the same key. For example, {@code String}, {@code @@ -35,13 +30,13 @@ public enum RequestKind { /** A default request for an instance. E.g.: {@code FooType} */ INSTANCE, - /** A request for a {@link Provider}. E.g.: {@code Provider<FooType>} */ + /** A request for a {@code Provider}. E.g.: {@code Provider<FooType>} */ PROVIDER, - /** A request for a {@link Lazy}. E.g.: {@code Lazy<FooType>} */ + /** A request for a {@code Lazy}. E.g.: {@code Lazy<FooType>} */ LAZY, - /** A request for a {@link Provider} of a {@link Lazy}. E.g.: {@code Provider<Lazy<FooType>>} */ + /** A request for a {@code Provider} of a {@code Lazy}. E.g.: {@code Provider<Lazy<FooType>>} */ PROVIDER_OF_LAZY, /** @@ -50,15 +45,15 @@ public enum RequestKind { */ MEMBERS_INJECTION, - /** A request for a {@link Producer}. E.g.: {@code Producer<FooType>} */ + /** A request for a {@code Producer}. E.g.: {@code Producer<FooType>} */ PRODUCER, - /** A request for a {@link Produced}. E.g.: {@code Produced<FooType>} */ + /** A request for a {@code Produced}. E.g.: {@code Produced<FooType>} */ PRODUCED, /** - * A request for a {@link com.google.common.util.concurrent.ListenableFuture}. E.g.: {@code - * ListenableFuture<FooType>}. These can only be requested by component interfaces. + * A request for a {@code ListenableFuture}. E.g.: {@code ListenableFuture<FooType>}. These can + * only be requested by component interfaces. */ FUTURE, ; diff --git a/java/dagger/model/Scope.java b/java/dagger/model/Scope.java index c7ebfa811..fe64c86d4 100644 --- a/java/dagger/model/Scope.java +++ b/java/dagger/model/Scope.java @@ -26,7 +26,6 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Equivalence; import com.squareup.javapoet.ClassName; import dagger.Reusable; -import dagger.producers.ProductionScope; import javax.inject.Singleton; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; @@ -88,7 +87,7 @@ public abstract class Scope { return isScope(REUSABLE); } - /** Returns {@code true} if this scope is the {@link ProductionScope @ProductionScope} scope. */ + /** Returns {@code true} if this scope is the {@code @ProductionScope} scope. */ public final boolean isProductionScope() { return isScope(PRODUCTION_SCOPE); } diff --git a/java/dagger/multibindings/LazyClassKey.java b/java/dagger/multibindings/LazyClassKey.java new file mode 100644 index 000000000..da46984e3 --- /dev/null +++ b/java/dagger/multibindings/LazyClassKey.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 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.multibindings; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import dagger.MapKey; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * A {@link MapKey} annotation for maps with {@code Class<?>} keys. + * + * <p>The difference from {@link ClassKey} is that dagger generates a string representation for the + * class to use under the hood, which prevents loading unused classes at runtime. + */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) +@Documented +@MapKey +public @interface LazyClassKey { + Class<?> value(); +} diff --git a/java/dagger/producers/internal/AbstractMapProducer.java b/java/dagger/producers/internal/AbstractMapProducer.java index c60f0cb07..360f1eb8e 100644 --- a/java/dagger/producers/internal/AbstractMapProducer.java +++ b/java/dagger/producers/internal/AbstractMapProducer.java @@ -17,12 +17,13 @@ package dagger.producers.internal; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.Providers.asDaggerProvider; import static dagger.producers.internal.Producers.producerFromProvider; import com.google.common.collect.ImmutableMap; +import dagger.internal.Provider; import dagger.producers.Producer; import java.util.Map; -import javax.inject.Provider; /** * An {@code abstract} {@link Producer} implementation used to implement {@link Map} bindings. @@ -76,6 +77,15 @@ abstract class AbstractMapProducer<K, V, V2> extends AbstractProducer<Map<K, V2> return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + Builder<K, V, V2> put(K key, javax.inject.Provider<V> providerOfValue) { + return put(key, asDaggerProvider(providerOfValue)); + } + /** Adds contributions from a super-implementation of a component into this builder. */ Builder<K, V, V2> putAll(Producer<Map<K, V2>> mapOfProducers) { if (mapOfProducers instanceof DelegateProducer) { diff --git a/java/dagger/producers/internal/AbstractProducesMethodProducer.java b/java/dagger/producers/internal/AbstractProducesMethodProducer.java index 0cf36ca53..95b8f8340 100644 --- a/java/dagger/producers/internal/AbstractProducesMethodProducer.java +++ b/java/dagger/producers/internal/AbstractProducesMethodProducer.java @@ -17,15 +17,16 @@ package dagger.producers.internal; import static dagger.internal.Preconditions.checkNotNull; +import static dagger.internal.Providers.asDaggerProvider; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import dagger.internal.Provider; import dagger.producers.monitoring.ProducerMonitor; import dagger.producers.monitoring.ProducerToken; import dagger.producers.monitoring.ProductionComponentMonitor; import java.util.concurrent.Executor; -import javax.inject.Provider; import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** @@ -54,6 +55,18 @@ public abstract class AbstractProducesMethodProducer<D, T> extends AbstractProdu this.executorProvider = checkNotNull(executorProvider); } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + protected AbstractProducesMethodProducer( + javax.inject.Provider<ProductionComponentMonitor> monitorProvider, + @NullableDecl ProducerToken token, + javax.inject.Provider<Executor> executorProvider) { + this(asDaggerProvider(monitorProvider), token, asDaggerProvider(executorProvider)); + } + @Override protected final ListenableFuture<T> compute() { monitor = monitorProvider.get().producerMonitorFor(token); diff --git a/java/dagger/producers/internal/DelegateProducer.java b/java/dagger/producers/internal/DelegateProducer.java index 6cc7547c3..de29234ac 100644 --- a/java/dagger/producers/internal/DelegateProducer.java +++ b/java/dagger/producers/internal/DelegateProducer.java @@ -20,8 +20,8 @@ import static dagger.internal.Preconditions.checkNotNull; import com.google.common.util.concurrent.ListenableFuture; import dagger.internal.DoubleCheck; +import dagger.internal.Provider; import dagger.producers.Producer; -import javax.inject.Provider; /** * A DelegateProducer that is used to stitch Producer indirection during initialization across diff --git a/java/dagger/producers/internal/MapOfProducedProducer.java b/java/dagger/producers/internal/MapOfProducedProducer.java index bd9f1bfcc..3db774094 100644 --- a/java/dagger/producers/internal/MapOfProducedProducer.java +++ b/java/dagger/producers/internal/MapOfProducedProducer.java @@ -18,6 +18,7 @@ package dagger.producers.internal; import static com.google.common.util.concurrent.Futures.transform; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static dagger.internal.Providers.asDaggerProvider; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; @@ -25,11 +26,11 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import dagger.internal.Provider; import dagger.producers.Produced; import dagger.producers.Producer; import java.util.List; import java.util.Map; -import javax.inject.Provider; /** * A {@link Producer} implementation used to implement {@link Map} bindings. This producer returns a @@ -108,6 +109,15 @@ public final class MapOfProducedProducer<K, V> extends AbstractMapProducer<K, V, return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> put(K key, javax.inject.Provider<V> providerOfValue) { + return put(key, asDaggerProvider(providerOfValue)); + } + @Override public Builder<K, V> putAll(Producer<Map<K, Produced<V>>> mapOfProducedProducer) { super.putAll(mapOfProducedProducer); diff --git a/java/dagger/producers/internal/MapOfProducerProducer.java b/java/dagger/producers/internal/MapOfProducerProducer.java index 064cf7499..145ea6db6 100644 --- a/java/dagger/producers/internal/MapOfProducerProducer.java +++ b/java/dagger/producers/internal/MapOfProducerProducer.java @@ -16,6 +16,7 @@ package dagger.producers.internal; +import static dagger.internal.Providers.asDaggerProvider; import static dagger.producers.internal.Producers.entryPointViewOf; import static dagger.producers.internal.Producers.nonCancellationPropagatingViewOf; @@ -24,9 +25,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import dagger.internal.Provider; import dagger.producers.Producer; import java.util.Map; -import javax.inject.Provider; /** * A {@link Producer} implementation used to implement {@link Map} bindings. This factory returns an @@ -65,6 +66,15 @@ public final class MapOfProducerProducer<K, V> extends AbstractMapProducer<K, V, return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> put(K key, javax.inject.Provider<V> providerOfValue) { + return put(key, asDaggerProvider(providerOfValue)); + } + @Override public Builder<K, V> putAll(Producer<Map<K, Producer<V>>> mapOfProducerProducer) { super.putAll(mapOfProducerProducer); diff --git a/java/dagger/producers/internal/MapProducer.java b/java/dagger/producers/internal/MapProducer.java index 8caeb45cb..c832ef402 100644 --- a/java/dagger/producers/internal/MapProducer.java +++ b/java/dagger/producers/internal/MapProducer.java @@ -17,18 +17,19 @@ package dagger.producers.internal; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static dagger.internal.Providers.asDaggerProvider; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import dagger.internal.Provider; import dagger.producers.Producer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.inject.Provider; /** * A {@link Producer} implementation used to implement {@link Map} bindings. This producer returns a @@ -62,6 +63,15 @@ public final class MapProducer<K, V> extends AbstractMapProducer<K, V, V> { return this; } + /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public Builder<K, V> put(K key, javax.inject.Provider<V> providerOfValue) { + return put(key, asDaggerProvider(providerOfValue)); + } + @Override public Builder<K, V> putAll(Producer<Map<K, V>> mapProducer) { super.putAll(mapProducer); diff --git a/java/dagger/producers/internal/Producers.java b/java/dagger/producers/internal/Producers.java index 54e4d5ee3..9385ee326 100644 --- a/java/dagger/producers/internal/Producers.java +++ b/java/dagger/producers/internal/Producers.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.catchingAsync; import static com.google.common.util.concurrent.Futures.transform; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static dagger.internal.Providers.asDaggerProvider; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; @@ -27,12 +28,12 @@ import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import dagger.internal.Provider; import dagger.producers.Produced; import dagger.producers.Producer; import java.util.List; import java.util.Map; import java.util.Set; -import javax.inject.Provider; /** * Utility methods for use in generated producer code. @@ -135,6 +136,15 @@ public final class Producers { } /** + * Legacy javax version of the method to support libraries compiled with an older version of + * Dagger. Do not use directly. + */ + @Deprecated + public static <T> Producer<T> producerFromProvider(final javax.inject.Provider<T> provider) { + return producerFromProvider(asDaggerProvider(provider)); + } + + /** * Returns a producer that succeeds with the given value. * * @deprecated Prefer the non-internal version of this method: {@link diff --git a/java/dagger/proguard.pro b/java/dagger/proguard.pro new file mode 100644 index 000000000..1bfc94749 --- /dev/null +++ b/java/dagger/proguard.pro @@ -0,0 +1,3 @@ +-keepclasseswithmembers,includedescriptorclasses class * { + @dagger.internal.KeepFieldType <fields>; +}
\ No newline at end of file diff --git a/java/dagger/r8.pro b/java/dagger/r8.pro new file mode 100644 index 000000000..17e0ffe6d --- /dev/null +++ b/java/dagger/r8.pro @@ -0,0 +1,3 @@ +-identifiernamestring @dagger.internal.IdentifierNameString class ** { + static java.lang.String *; +}
\ No newline at end of file diff --git a/java/dagger/spi/BUILD b/java/dagger/spi/BUILD index 13428c1a4..8084e6396 100644 --- a/java/dagger/spi/BUILD +++ b/java/dagger/spi/BUILD @@ -63,7 +63,6 @@ gen_maven_artifact( ], artifact_target_maven_deps = [ "com.google.code.findbugs:jsr305", - "com.google.dagger:dagger-producers", "com.google.dagger:dagger", "com.google.devtools.ksp:symbol-processing-api", "com.google.guava:failureaccess", diff --git a/java/dagger/spi/model/BUILD b/java/dagger/spi/model/BUILD index e9346ddf6..e5af4e50b 100644 --- a/java/dagger/spi/model/BUILD +++ b/java/dagger/spi/model/BUILD @@ -40,7 +40,6 @@ kt_jvm_library( deps = [ "//java/dagger:core", "//java/dagger/internal/codegen/extension", - "//java/dagger/producers", "//third_party/java/auto:common", "//third_party/java/auto:value", "//third_party/java/error_prone:annotations", @@ -48,7 +47,6 @@ kt_jvm_library( "//third_party/java/guava/collect", "//third_party/java/guava/graph", "//third_party/java/javapoet", - "//third_party/java/jsr305_annotations", "//third_party/java/jsr330_inject", "@maven//:com_google_devtools_ksp_symbol_processing_api", ], diff --git a/java/dagger/spi/model/BindingGraph.java b/java/dagger/spi/model/BindingGraph.java index aab7c2a4b..7f433a1e0 100644 --- a/java/dagger/spi/model/BindingGraph.java +++ b/java/dagger/spi/model/BindingGraph.java @@ -41,14 +41,14 @@ import java.util.stream.Stream; * <p>A {@link BindingGraph} represents one of the following: * * <ul> - * <li>an entire component hierarchy rooted at a {@link dagger.Component} or {@link - * dagger.producers.ProductionComponent} - * <li>a partial component hierarchy rooted at a {@link dagger.Subcomponent} or {@link - * dagger.producers.ProductionSubcomponent} (only when the value of {@code - * -Adagger.fullBindingGraphValidation} is not {@code NONE}) - * <li>the bindings installed by a {@link Module} or {@link dagger.producers.ProducerModule}, - * including all subcomponents generated by {@link Module#subcomponents()} ()} and {@link - * dagger.producers.ProducerModule#subcomponents()} ()} + * <li>an entire component hierarchy rooted at a {@link dagger.Component} or + * {@code ProductionComponent} + * <li>a partial component hierarchy rooted at a {@link dagger.Subcomponent} or + * {@code ProductionSubcomponent} (only when the value of + * {@code -Adagger.fullBindingGraphValidation} is not {@code NONE}) + * <li>the bindings installed by a {@link Module} or {@code ProducerModule}, + * including all subcomponents generated by {@link Module#subcomponents()} ()} and + * {@code ProducerModule#subcomponents()} ()} * </ul> * * In the case of a {@link BindingGraph} representing a module, the root {@link ComponentNode} will diff --git a/java/dagger/spi/model/BindingKind.java b/java/dagger/spi/model/BindingKind.java index b854b5300..eb1e83501 100644 --- a/java/dagger/spi/model/BindingKind.java +++ b/java/dagger/spi/model/BindingKind.java @@ -34,8 +34,8 @@ public enum BindingKind { ASSISTED_FACTORY, /** - * An implicit binding for a {@link dagger.Component}- or {@link - * dagger.producers.ProductionComponent}-annotated type. + * An implicit binding for a {@link dagger.Component}- or {@code ProductionComponent}-annotated + * type. */ COMPONENT, @@ -65,16 +65,14 @@ public enum BindingKind { /** A binding for a {@link dagger.BindsInstance}-annotated builder method. */ BOUND_INSTANCE, - /** A binding for a {@link dagger.producers.Produces}-annotated method. */ + /** A binding for a {@code Produces}-annotated method. */ PRODUCTION, /** - * A binding for a production method on a production component's {@linkplain - * dagger.producers.ProductionComponent#dependencies()} dependency} that returns a {@link - * com.google.common.util.concurrent.ListenableFuture} or {@link - * com.google.common.util.concurrent.FluentFuture}. Methods on production component dependencies - * that don't return a future are considered {@linkplain #COMPONENT_PROVISION component provision - * bindings}. + * A binding for a production method on a production component's + * {@code ProductionComponent#dependencies()} that returns a {@code ListenableFuture} or + * {@code FluentFuture}. Methods on production component dependencies + * that don't return a future are considered component provision bindings. */ COMPONENT_PRODUCTION, diff --git a/java/dagger/spi/model/ComponentPath.java b/java/dagger/spi/model/ComponentPath.java index 63a5f6b90..11c811e26 100644 --- a/java/dagger/spi/model/ComponentPath.java +++ b/java/dagger/spi/model/ComponentPath.java @@ -39,8 +39,7 @@ public abstract class ComponentPath { public abstract ImmutableList<DaggerTypeElement> components(); /** - * Returns the root {@link dagger.Component}- or {@link - * dagger.producers.ProductionComponent}-annotated type + * Returns the root {@code Component}- or {@code ProductionComponent}-annotated type */ public final DaggerTypeElement rootComponent() { return components().get(0); @@ -89,7 +88,7 @@ public abstract class ComponentPath { @Override public final String toString() { - return components().stream().map(DaggerTypeElement::qualifiedName).collect(joining(" → ")); + return components().stream().map(Key::qualifiedName).collect(joining(" → ")); } @Memoized diff --git a/java/dagger/spi/model/DaggerAnnotation.java b/java/dagger/spi/model/DaggerAnnotation.java index 2ec66c010..5b590033f 100644 --- a/java/dagger/spi/model/DaggerAnnotation.java +++ b/java/dagger/spi/model/DaggerAnnotation.java @@ -16,55 +16,29 @@ package dagger.spi.model; -import com.google.auto.common.AnnotationMirrors; -import com.google.auto.value.AutoValue; import com.google.devtools.ksp.symbol.KSAnnotation; -import javax.annotation.Nullable; +import com.google.errorprone.annotations.DoNotMock; import javax.lang.model.element.AnnotationMirror; /** Wrapper type for an annotation. */ -@AutoValue +@DoNotMock("Only use real implementations created by Dagger") public abstract class DaggerAnnotation { - public static DaggerAnnotation fromJavac( - DaggerTypeElement annotationTypeElement, AnnotationMirror annotation) { - return new AutoValue_DaggerAnnotation(annotationTypeElement, annotation, null); - } - - public static DaggerAnnotation fromKsp( - DaggerTypeElement annotationTypeElement, KSAnnotation ksp) { - return new AutoValue_DaggerAnnotation(annotationTypeElement, null, ksp); - } - public abstract DaggerTypeElement annotationTypeElement(); /** - * java representation for the annotation, returns {@code null} if the annotation isn't a java - * element. + * Returns the Javac representation for the annotation. + * + * @throws IllegalStateException if the current backend isn't Javac. */ - @Nullable - public abstract AnnotationMirror java(); + public abstract AnnotationMirror javac(); - /** KSP declaration for the annotation, returns {@code null} not using KSP. */ - @Nullable + /** + * Returns the KSP representation for the annotation. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract KSAnnotation ksp(); - public DaggerProcessingEnv.Backend backend() { - if (java() != null) { - return DaggerProcessingEnv.Backend.JAVAC; - } else if (ksp() != null) { - return DaggerProcessingEnv.Backend.KSP; - } - throw new AssertionError("Unexpected backend"); - } - - @Override - public final String toString() { - switch (backend()) { - case JAVAC: - return AnnotationMirrors.toString(java()); - case KSP: - return ksp().toString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } + /** Returns the backend used in this compilation. */ + public abstract DaggerProcessingEnv.Backend backend(); } diff --git a/java/dagger/spi/model/DaggerElement.java b/java/dagger/spi/model/DaggerElement.java index 0f6a2cf35..0c0c53ebf 100644 --- a/java/dagger/spi/model/DaggerElement.java +++ b/java/dagger/spi/model/DaggerElement.java @@ -16,49 +16,27 @@ package dagger.spi.model; -import com.google.auto.value.AutoValue; import com.google.devtools.ksp.symbol.KSAnnotated; -import javax.annotation.Nullable; +import com.google.errorprone.annotations.DoNotMock; import javax.lang.model.element.Element; /** Wrapper type for an element. */ -@AutoValue +@DoNotMock("Only use real implementations created by Dagger") public abstract class DaggerElement { - public static DaggerElement fromJavac(Element element) { - return new AutoValue_DaggerElement(element, null); - } - - public static DaggerElement fromKsp(KSAnnotated ksp) { - return new AutoValue_DaggerElement(null, ksp); - } - /** - * Java representation for the element, returns {@code null} not using java annotation processor. + * Returns the Javac representation for the element. + * + * @throws IllegalStateException if the current backend isn't Javac. */ - @Nullable - public abstract Element java(); + public abstract Element javac(); - /** KSP declaration for the element, returns {@code null} not using KSP. */ - @Nullable + /** + * Returns the KSP representation for the element. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract KSAnnotated ksp(); - public DaggerProcessingEnv.Backend backend() { - if (java() != null) { - return DaggerProcessingEnv.Backend.JAVAC; - } else if (ksp() != null) { - return DaggerProcessingEnv.Backend.KSP; - } - throw new AssertionError("Unexpected backend"); - } - - @Override - public final String toString() { - switch (backend()) { - case JAVAC: - return java().toString(); - case KSP: - return ksp().toString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } + /** Returns the backend used in this compilation. */ + public abstract DaggerProcessingEnv.Backend backend(); } diff --git a/java/dagger/spi/model/DaggerExecutableElement.java b/java/dagger/spi/model/DaggerExecutableElement.java index 7df6a1e38..afbd8e001 100644 --- a/java/dagger/spi/model/DaggerExecutableElement.java +++ b/java/dagger/spi/model/DaggerExecutableElement.java @@ -16,59 +16,27 @@ package dagger.spi.model; -import com.google.auto.value.AutoValue; import com.google.devtools.ksp.symbol.KSFunctionDeclaration; -import javax.annotation.Nullable; +import com.google.errorprone.annotations.DoNotMock; import javax.lang.model.element.ExecutableElement; /** Wrapper type for an executable element. */ -@AutoValue +@DoNotMock("Only use real implementations created by Dagger") public abstract class DaggerExecutableElement { - public static DaggerExecutableElement fromJava(ExecutableElement executableElement) { - return new AutoValue_DaggerExecutableElement(executableElement, null); - } - - public static DaggerExecutableElement fromKsp(KSFunctionDeclaration declaration) { - return new AutoValue_DaggerExecutableElement(null, declaration); - } - /** - * Java representation for the element, returns {@code null} not using java annotation processor. + * Returns the Javac representation for the executable element. + * + * @throws IllegalStateException if the current backend isn't Javac. */ - @Nullable - public abstract ExecutableElement java(); + public abstract ExecutableElement javac(); - /** KSP declaration for the element, returns {@code null} not using KSP. */ - @Nullable + /** + * Returns the KSP representation for the executable element. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract KSFunctionDeclaration ksp(); - public DaggerProcessingEnv.Backend backend() { - if (java() != null) { - return DaggerProcessingEnv.Backend.JAVAC; - } else if (ksp() != null) { - return DaggerProcessingEnv.Backend.KSP; - } - throw new AssertionError("Unexpected backend"); - } - - @Override - public final String toString() { - switch (backend()) { - case JAVAC: - return java().toString(); - case KSP: - return ksp().toString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } - - String simpleName() { - switch (backend()) { - case JAVAC: - return java().getSimpleName().toString(); - case KSP: - return ksp().getSimpleName().toString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } + /** Returns the backend used in this compilation. */ + public abstract DaggerProcessingEnv.Backend backend(); } diff --git a/java/dagger/spi/model/DaggerProcessingEnv.java b/java/dagger/spi/model/DaggerProcessingEnv.java index 8c652c89d..ab2e33e5f 100644 --- a/java/dagger/spi/model/DaggerProcessingEnv.java +++ b/java/dagger/spi/model/DaggerProcessingEnv.java @@ -16,50 +16,41 @@ package dagger.spi.model; -import com.google.auto.value.AutoValue; import com.google.devtools.ksp.processing.Resolver; import com.google.devtools.ksp.processing.SymbolProcessorEnvironment; -import javax.annotation.Nullable; +import com.google.errorprone.annotations.DoNotMock; import javax.annotation.processing.ProcessingEnvironment; /** Wrapper type for an element. */ -@AutoValue +@DoNotMock("Only use real implementations created by Dagger") public abstract class DaggerProcessingEnv { /** Represents a type of backend used for compilation. */ - public enum Backend { JAVAC, KSP } - - public static boolean isJavac(Backend backend) { - return backend.equals(Backend.JAVAC); - } - - public static DaggerProcessingEnv fromJavac(ProcessingEnvironment env) { - return new AutoValue_DaggerProcessingEnv(env, null, null); - } - - public static DaggerProcessingEnv fromKsp(SymbolProcessorEnvironment env, Resolver resolver) { - return new AutoValue_DaggerProcessingEnv(null, env, resolver); + public enum Backend { + JAVAC, + KSP } /** - * Java representation for the processing environment, returns {@code null} not using java - * annotation processor. + * Returns the Javac representation for the processing environment. + * + * @throws IllegalStateException if the current backend isn't Javac. */ - @Nullable - public abstract ProcessingEnvironment java(); + public abstract ProcessingEnvironment javac(); - @Nullable + /** + * Returns the KSP representation for the processing environment. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract SymbolProcessorEnvironment ksp(); - @Nullable + /** + * Returns the KSP representation for the resolver. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract Resolver resolver(); /** Returns the backend used in this compilation. */ - public DaggerProcessingEnv.Backend backend() { - if (java() != null) { - return DaggerProcessingEnv.Backend.JAVAC; - } else if (ksp() != null) { - return DaggerProcessingEnv.Backend.KSP; - } - throw new AssertionError("Unexpected backend"); - } + public abstract DaggerProcessingEnv.Backend backend(); } diff --git a/java/dagger/spi/model/DaggerType.java b/java/dagger/spi/model/DaggerType.java index 249d49492..463350606 100644 --- a/java/dagger/spi/model/DaggerType.java +++ b/java/dagger/spi/model/DaggerType.java @@ -16,47 +16,27 @@ package dagger.spi.model; -import com.google.auto.value.AutoValue; import com.google.devtools.ksp.symbol.KSType; -import javax.annotation.Nullable; +import com.google.errorprone.annotations.DoNotMock; import javax.lang.model.type.TypeMirror; /** Wrapper type for a type. */ -@AutoValue +@DoNotMock("Only use real implementations created by Dagger") public abstract class DaggerType { - public static DaggerType fromJavac(TypeMirror type) { - return new AutoValue_DaggerType(type, null); - } - - public static DaggerType fromKsp(KSType type) { - return new AutoValue_DaggerType(null, type); - } - - /** Java representation for the type, returns {@code null} not using java annotation processor. */ - @Nullable - public abstract TypeMirror java(); - - /** KSP declaration for the type, returns {@code null} not using KSP. */ - @Nullable + /** + * Returns the Javac representation for the type. + * + * @throws IllegalStateException if the current backend isn't Javac. + */ + public abstract TypeMirror javac(); + + /** + * Returns the KSP representation for the type. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract KSType ksp(); - public DaggerProcessingEnv.Backend backend() { - if (java() != null) { - return DaggerProcessingEnv.Backend.JAVAC; - } else if (ksp() != null) { - return DaggerProcessingEnv.Backend.KSP; - } - throw new AssertionError("Unexpected backend"); - } - - @Override - public final String toString() { - switch (backend()) { - case JAVAC: - return java().toString(); - case KSP: - return ksp().toString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } + /** Returns the backend used in this compilation. */ + public abstract DaggerProcessingEnv.Backend backend(); } diff --git a/java/dagger/spi/model/DaggerTypeElement.java b/java/dagger/spi/model/DaggerTypeElement.java index 2f70a54b8..935770d11 100644 --- a/java/dagger/spi/model/DaggerTypeElement.java +++ b/java/dagger/spi/model/DaggerTypeElement.java @@ -16,78 +16,27 @@ package dagger.spi.model; -import com.google.auto.common.MoreElements; -import com.google.auto.value.AutoValue; import com.google.devtools.ksp.symbol.KSClassDeclaration; -import javax.annotation.Nullable; +import com.google.errorprone.annotations.DoNotMock; import javax.lang.model.element.TypeElement; /** Wrapper type for a type element. */ -@AutoValue +@DoNotMock("Only use real implementations created by Dagger") public abstract class DaggerTypeElement { - public static DaggerTypeElement fromJavac(@Nullable TypeElement element) { - return new AutoValue_DaggerTypeElement(element, null); - } - - public static DaggerTypeElement fromKsp(@Nullable KSClassDeclaration declaration) { - return new AutoValue_DaggerTypeElement(null, declaration); - } - - /** Java representation for the type, returns {@code null} not using java annotation processor. */ - @Nullable - public abstract TypeElement java(); - - /** KSP declaration for the element, returns {@code null} not using KSP. */ - @Nullable + /** + * Returns the Javac representation for the type element. + * + * @throws IllegalStateException if the current backend isn't Javac. + */ + public abstract TypeElement javac(); + + /** + * Returns the KSP representation for the type element. + * + * @throws IllegalStateException if the current backend isn't KSP. + */ public abstract KSClassDeclaration ksp(); - public final boolean hasAnnotation(String annotationName) { - switch (backend()) { - case JAVAC: - return MoreElements.isAnnotationPresent(java(), annotationName); - case KSP: - return KspUtilsKt.hasAnnotation(ksp(), annotationName); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } - - public String packageName() { - switch (backend()) { - case JAVAC: - return MoreElements.getPackage(java()).getQualifiedName().toString(); - case KSP: - return KspUtilsKt.getNormalizedPackageName(ksp()); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } - - public String qualifiedName() { - switch (backend()) { - case JAVAC: - return java().getQualifiedName().toString(); - case KSP: - return ksp().getQualifiedName().asString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } - - public DaggerProcessingEnv.Backend backend() { - if (java() != null) { - return DaggerProcessingEnv.Backend.JAVAC; - } else if (ksp() != null) { - return DaggerProcessingEnv.Backend.KSP; - } - throw new AssertionError("Unexpected backend"); - } - - @Override - public final String toString() { - switch (backend()) { - case JAVAC: - return java().toString(); - case KSP: - return ksp().toString(); - } - throw new IllegalStateException(String.format("Backend %s not supported yet.", backend())); - } + /** Returns the backend used in this compilation. */ + public abstract DaggerProcessingEnv.Backend backend(); } diff --git a/java/dagger/spi/model/Key.java b/java/dagger/spi/model/Key.java index d871e6d12..d4478f6a6 100644 --- a/java/dagger/spi/model/Key.java +++ b/java/dagger/spi/model/Key.java @@ -130,7 +130,7 @@ public abstract class Key { private static MultibindingContributionIdentifier create( DaggerTypeElement contributingModule, DaggerExecutableElement bindingMethod) { return new AutoValue_Key_MultibindingContributionIdentifier( - contributingModule.qualifiedName(), bindingMethod.simpleName()); + qualifiedName(contributingModule), simpleName(bindingMethod)); } /** Returns the module containing the multibinding method. */ @@ -150,4 +150,24 @@ public abstract class Key { return String.format("%s#%s", contributingModule(), bindingMethod()); } } + + static String qualifiedName(DaggerTypeElement element) { + switch (element.backend()) { + case JAVAC: + return element.javac().getQualifiedName().toString(); + case KSP: + return element.ksp().getQualifiedName().asString(); + } + throw new IllegalStateException("Unknown backend: " + element.backend()); + } + + private static String simpleName(DaggerExecutableElement element) { + switch (element.backend()) { + case JAVAC: + return element.javac().getSimpleName().toString(); + case KSP: + return element.ksp().getSimpleName().asString(); + } + throw new IllegalStateException("Unknown backend: " + element.backend()); + } } diff --git a/java/dagger/spi/model/MoreAnnotationMirrors.java b/java/dagger/spi/model/MoreAnnotationMirrors.java index 44c0363c9..a5c04c6af 100644 --- a/java/dagger/spi/model/MoreAnnotationMirrors.java +++ b/java/dagger/spi/model/MoreAnnotationMirrors.java @@ -35,7 +35,7 @@ final class MoreAnnotationMirrors { * defined in the annotation type. */ public static String toStableString(DaggerAnnotation qualifier) { - return stableAnnotationMirrorToString(qualifier.java()); + return stableAnnotationMirrorToString(qualifier.javac()); } /** diff --git a/java/dagger/spi/model/RequestKind.java b/java/dagger/spi/model/RequestKind.java index 62f46cd65..bcbe26d4f 100644 --- a/java/dagger/spi/model/RequestKind.java +++ b/java/dagger/spi/model/RequestKind.java @@ -19,11 +19,6 @@ package dagger.spi.model; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; -import dagger.Lazy; -import dagger.producers.Produced; -import dagger.producers.Producer; -import javax.inject.Provider; - /** * Represents the different kinds of {@link javax.lang.model.type.TypeMirror types} that may be * requested as dependencies for the same key. For example, {@code String}, {@code @@ -35,13 +30,13 @@ public enum RequestKind { /** A default request for an instance. E.g.: {@code FooType} */ INSTANCE, - /** A request for a {@link Provider}. E.g.: {@code Provider<FooType>} */ + /** A request for a {@code Provider}. E.g.: {@code Provider<FooType>} */ PROVIDER, - /** A request for a {@link Lazy}. E.g.: {@code Lazy<FooType>} */ + /** A request for a {@code Lazy}. E.g.: {@code Lazy<FooType>} */ LAZY, - /** A request for a {@link Provider} of a {@link Lazy}. E.g.: {@code Provider<Lazy<FooType>>} */ + /** A request for a {@code Provider} of a {@code Lazy}. E.g.: {@code Provider<Lazy<FooType>>}. */ PROVIDER_OF_LAZY, /** @@ -50,15 +45,15 @@ public enum RequestKind { */ MEMBERS_INJECTION, - /** A request for a {@link Producer}. E.g.: {@code Producer<FooType>} */ + /** A request for a {@code Producer}. E.g.: {@code Producer<FooType>} */ PRODUCER, - /** A request for a {@link Produced}. E.g.: {@code Produced<FooType>} */ + /** A request for a {@code Produced}. E.g.: {@code Produced<FooType>} */ PRODUCED, /** - * A request for a {@link com.google.common.util.concurrent.ListenableFuture}. E.g.: {@code - * ListenableFuture<FooType>}. These can only be requested by component interfaces. + * A request for a {@code ListenableFuture}. E.g.: {@code ListenableFuture<FooType>}. These can + * only be requested by component interfaces. */ FUTURE, ; diff --git a/java/dagger/spi/model/Scope.java b/java/dagger/spi/model/Scope.java index d1505b969..2d86101f1 100644 --- a/java/dagger/spi/model/Scope.java +++ b/java/dagger/spi/model/Scope.java @@ -18,6 +18,7 @@ package dagger.spi.model; import static com.google.common.base.Preconditions.checkArgument; +import com.google.auto.common.MoreElements; import com.google.auto.value.AutoValue; /** A representation of a {@link javax.inject.Scope}. */ @@ -42,8 +43,16 @@ public abstract class Scope { * Returns {@code true} if {@code scopeAnnotationType} is a {@link javax.inject.Scope} annotation. */ public static boolean isScope(DaggerTypeElement scopeAnnotationType) { - return scopeAnnotationType.hasAnnotation(SCOPE) - || scopeAnnotationType.hasAnnotation(SCOPE_JAVAX); + switch (scopeAnnotationType.backend()) { + case JAVAC: + return MoreElements.isAnnotationPresent(scopeAnnotationType.javac(), SCOPE) + || MoreElements.isAnnotationPresent(scopeAnnotationType.javac(), SCOPE_JAVAX); + case KSP: + return KspUtilsKt.hasAnnotation(scopeAnnotationType.ksp(), SCOPE) + || KspUtilsKt.hasAnnotation(scopeAnnotationType.ksp(), SCOPE_JAVAX); + } + throw new IllegalStateException( + String.format("Backend %s not supported yet.", scopeAnnotationType.backend())); } private boolean isScope(String annotationName) { @@ -71,8 +80,7 @@ public abstract class Scope { } /** - * Returns {@code true} if this scope is the {@link - * dagger.producers.ProductionScope @ProductionScope} scope. + * Returns {@code true} if this scope is the {@code @ProductionScope} scope. */ public final boolean isProductionScope() { return isScope(PRODUCTION_SCOPE); diff --git a/java/dagger/spi/model/testing/BindingGraphSubject.java b/java/dagger/spi/model/testing/BindingGraphSubject.java index e2067318b..4d53d9367 100644 --- a/java/dagger/spi/model/testing/BindingGraphSubject.java +++ b/java/dagger/spi/model/testing/BindingGraphSubject.java @@ -112,7 +112,7 @@ public final class BindingGraphSubject extends Subject { private static String formattedType(DaggerType type) { switch (type.backend()) { case JAVAC: - return type.java().toString(); + return type.javac().toString(); case KSP: return type.ksp().getDeclaration().getQualifiedName().asString(); } diff --git a/java/dagger/testing/compile/BUILD b/java/dagger/testing/compile/BUILD index 585939867..a0e09a814 100644 --- a/java/dagger/testing/compile/BUILD +++ b/java/dagger/testing/compile/BUILD @@ -26,6 +26,9 @@ java_library( "CompilerProcessors.java", "CompilerTests.java", ], + exports = [ + "//java/dagger/internal/codegen/xprocessing:xprocessing-testing", + ], deps = [ "//java/dagger/internal/codegen:processor", "//java/dagger/internal/codegen/extension", diff --git a/java/dagger/testing/compile/CompilerTests.java b/java/dagger/testing/compile/CompilerTests.java index 55574201e..ac74a618e 100644 --- a/java/dagger/testing/compile/CompilerTests.java +++ b/java/dagger/testing/compile/CompilerTests.java @@ -21,6 +21,7 @@ import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.collect.Streams.stream; import static com.google.testing.compile.Compiler.javac; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static java.util.stream.Collectors.toMap; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XProcessingEnvConfig; @@ -39,6 +40,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.io.Files; +import com.google.devtools.ksp.processing.SymbolProcessorProvider; import com.google.testing.compile.Compiler; import dagger.internal.codegen.ComponentProcessor; import dagger.internal.codegen.KspComponentProcessor; @@ -46,11 +48,13 @@ import dagger.spi.model.BindingGraphPlugin; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.function.Consumer; +import javax.annotation.processing.Processor; import org.junit.rules.TemporaryFolder; /** A helper class for working with java compiler tests. */ @@ -150,6 +154,8 @@ public final class CompilerTests { // Set default values return builder .processorOptions(DEFAULT_PROCESSOR_OPTIONS) + .additionalJavacProcessors(ImmutableList.of()) + .additionalKspProcessors(ImmutableList.of()) .processingStepSuppliers(ImmutableSet.of()) .bindingGraphPluginSuppliers(ImmutableSet.of()); } @@ -160,6 +166,12 @@ public final class CompilerTests { /** Returns the annotation processor options */ abstract ImmutableMap<String, String> processorOptions(); + /** Returns the extra Javac processors. */ + abstract ImmutableCollection<Processor> additionalJavacProcessors(); + + /** Returns the extra KSP processors. */ + abstract ImmutableCollection<SymbolProcessorProvider> additionalKspProcessors(); + /** Returns the processing steps suppliers. */ abstract ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers(); @@ -192,6 +204,16 @@ public final class CompilerTests { return toBuilder().processorOptions(newProcessorOptions).build(); } + /** Returns a new {@link HiltCompiler} instance with the additional Javac processors. */ + public DaggerCompiler withAdditionalJavacProcessors(Processor... processors) { + return toBuilder().additionalJavacProcessors(ImmutableList.copyOf(processors)).build(); + } + + /** Returns a new {@link HiltCompiler} instance with the additional KSP processors. */ + public DaggerCompiler withAdditionalKspProcessors(SymbolProcessorProvider... processors) { + return toBuilder().additionalKspProcessors(ImmutableList.copyOf(processors)).build(); + } + /** Returns a new {@link Compiler} instance with the given processing steps. */ public DaggerCompiler withProcessingSteps(Supplier<XProcessingStep>... suppliers) { return toBuilder().processingStepSuppliers(ImmutableList.copyOf(suppliers)).build(); @@ -210,23 +232,43 @@ public final class CompilerTests { /* kotlincArguments= */ ImmutableList.of( "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"), /* config= */ PROCESSING_ENV_CONFIG, - /* javacProcessors= */ ImmutableList.of( - ComponentProcessor.withTestPlugins(bindingGraphPlugins()), - new CompilerProcessors.JavacProcessor(processingSteps())), - /* symbolProcessorProviders= */ ImmutableList.of( - KspComponentProcessor.Provider.withTestPlugins(bindingGraphPlugins()), - new CompilerProcessors.KspProcessor.Provider(processingSteps())), + /* javacProcessors= */ mergeProcessors( + ImmutableList.of( + ComponentProcessor.withTestPlugins(bindingGraphPlugins()), + new CompilerProcessors.JavacProcessor(processingSteps())), + additionalJavacProcessors()), + /* symbolProcessorProviders= */ mergeProcessors( + ImmutableList.of( + KspComponentProcessor.Provider.withTestPlugins(bindingGraphPlugins()), + new CompilerProcessors.KspProcessor.Provider(processingSteps())), + additionalKspProcessors()), result -> { onCompilationResult.accept(result); return null; }); } + private static <T> ImmutableList<T> mergeProcessors( + Collection<T> defaultProcessors, Collection<T> extraProcessors) { + Map<Class<?>, T> processors = + defaultProcessors.stream() + .collect(toMap(Object::getClass, (T e) -> e, (p1, p2) -> p2, HashMap::new)); + // Adds extra processors, and allows overriding any processors of the same class. + extraProcessors.forEach(processor -> processors.put(processor.getClass(), processor)); + return ImmutableList.copyOf(processors.values()); + } + /** Used to build a {@link DaggerCompiler}. */ @AutoValue.Builder public abstract static class Builder { abstract Builder sources(ImmutableCollection<Source> sources); abstract Builder processorOptions(Map<String, String> processorOptions); + + abstract Builder additionalJavacProcessors(ImmutableCollection<Processor> processors); + + abstract Builder additionalKspProcessors( + ImmutableCollection<SymbolProcessorProvider> processors); + abstract Builder processingStepSuppliers( ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers); abstract Builder bindingGraphPluginSuppliers( |