Skip to content

Commit

Permalink
Support requesting Jakarta Provider types.
Browse files Browse the repository at this point in the history
RELNOTES=Support Jakarta Provider requests
PiperOrigin-RevId: 696637506
  • Loading branch information
Chang-Eric authored and Dagger Team committed Nov 14, 2024
1 parent a065c9f commit d0b2cc1
Show file tree
Hide file tree
Showing 84 changed files with 955 additions and 128 deletions.
17 changes: 11 additions & 6 deletions java/dagger/internal/codegen/base/FrameworkTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package dagger.internal.codegen.base;

import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;

import androidx.room.compiler.processing.XType;
Expand All @@ -32,7 +31,11 @@
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);
ImmutableSet.of(
TypeNames.PROVIDER,
TypeNames.JAKARTA_PROVIDER,
TypeNames.LAZY,
TypeNames.MEMBERS_INJECTOR);

// NOTE(beder): ListenableFuture is not considered a producer framework type because it is not
// defined by the framework, so we can't treat it specially in ordinary Dagger.
Expand All @@ -42,13 +45,15 @@ public final class FrameworkTypes {
private static final ImmutableSet<ClassName> ALL_FRAMEWORK_TYPES =
ImmutableSet.<ClassName>builder().addAll(PROVISION_TYPES).addAll(PRODUCTION_TYPES).build();

private static final ImmutableSet<ClassName> SET_VALUE_FRAMEWORK_TYPES =
public static final ImmutableSet<ClassName> SET_VALUE_FRAMEWORK_TYPES =
ImmutableSet.of(TypeNames.PRODUCED);

public static final ImmutableSet<ClassName> MAP_VALUE_FRAMEWORK_TYPES =
MapType.VALID_FRAMEWORK_REQUEST_KINDS.stream()
.map(RequestKinds::frameworkClassName)
.collect(toImmutableSet());
ImmutableSet.of(
TypeNames.PRODUCED,
TypeNames.PRODUCER,
TypeNames.PROVIDER,
TypeNames.JAKARTA_PROVIDER);

/** Returns true if the type represents a producer-related framework type. */
public static boolean isProducerType(XType type) {
Expand Down
21 changes: 15 additions & 6 deletions java/dagger/internal/codegen/base/MapType.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
@AutoValue
public abstract class MapType {
// TODO(b/28555349): support PROVIDER_OF_LAZY here too
// TODO(b/376124787): We could consolidate this with a similar list in FrameworkTypes
// if we had a better way to go from RequestKind to framework ClassName or vice versa
/** The valid framework request kinds allowed on a multibinding map value. */
public static final ImmutableSet<RequestKind> VALID_FRAMEWORK_REQUEST_KINDS =
private static final ImmutableSet<RequestKind> VALID_FRAMEWORK_REQUEST_KINDS =
ImmutableSet.of(RequestKind.PROVIDER, RequestKind.PRODUCER, RequestKind.PRODUCED);

private XType type;
Expand Down Expand Up @@ -107,12 +109,19 @@ public XType unwrappedFrameworkValueType() {
*/
public RequestKind valueRequestKind() {
checkArgument(!isRawType());
for (RequestKind frameworkRequestKind : VALID_FRAMEWORK_REQUEST_KINDS) {
if (valuesAreTypeOf(RequestKinds.frameworkClassName(frameworkRequestKind))) {
return frameworkRequestKind;
}
RequestKind requestKind = RequestKinds.getRequestKind(valueType());
if (VALID_FRAMEWORK_REQUEST_KINDS.contains(requestKind)) {
return requestKind;
} else if (requestKind == RequestKind.PROVIDER_OF_LAZY) {
// This is kind of a weird case. We don't support Map<K, Lazy<V>>, so we also don't support
// Map<K, Provider<Lazy<V>>> directly. However, if the user bound that themselves, we don't
// want that to get confused as a normal instance request, so return PROVIDER here.
return RequestKind.PROVIDER;
} else {
// Not all RequestKinds are supported, so if there's a map value that matches an unsupported
// RequestKind, just treat it like it is a normal instance request.
return RequestKind.INSTANCE;
}
return RequestKind.INSTANCE;
}

/** {@code true} if {@code type} is a {@link java.util.Map} type. */
Expand Down
9 changes: 8 additions & 1 deletion java/dagger/internal/codegen/base/RequestKinds.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ public static TypeName requestTypeName(RequestKind requestKind, TypeName keyType

private static final ImmutableMap<RequestKind, ClassName> FRAMEWORK_CLASSES =
ImmutableMap.of(
// Default to the javax Provider since that is what is used for the binding graph
// representation.
PROVIDER, TypeNames.PROVIDER,
LAZY, TypeNames.LAZY,
PRODUCER, TypeNames.PRODUCER,
Expand All @@ -111,10 +113,15 @@ public static RequestKind getRequestKind(XType type) {
return RequestKind.INSTANCE;
}

if (isTypeOf(type, TypeNames.PROVIDER) && isTypeOf(unwrapType(type), TypeNames.LAZY)) {
if ((isTypeOf(type, TypeNames.PROVIDER) || isTypeOf(type, TypeNames.JAKARTA_PROVIDER))
&& isTypeOf(unwrapType(type), TypeNames.LAZY)) {
return RequestKind.PROVIDER_OF_LAZY;
}

if (isTypeOf(type, TypeNames.JAKARTA_PROVIDER)) {
return RequestKind.PROVIDER;
}

return FRAMEWORK_CLASSES.keySet().stream()
.filter(kind -> isTypeOf(type, FRAMEWORK_CLASSES.get(kind)))
.collect(toOptional())
Expand Down
13 changes: 5 additions & 8 deletions java/dagger/internal/codegen/binding/ComponentDeclarations.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimaps;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.WildcardTypeName;
import dagger.internal.codegen.base.DaggerSuperficialValidation;
import dagger.internal.codegen.base.FrameworkTypes;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.DaggerAnnotation;
import dagger.internal.codegen.model.Key;
Expand All @@ -38,11 +40,6 @@

/** Stores the bindings and declarations of a component by key. */
final class ComponentDeclarations {
private static final ImmutableSet<TypeName> MAP_FRAMEWORK_TYPENAMES =
ImmutableSet.of(TypeNames.PROVIDER, TypeNames.PRODUCER, TypeNames.PRODUCED);
private static final ImmutableSet<TypeName> SET_FRAMEWORK_TYPENAMES =
ImmutableSet.of(TypeNames.PRODUCED);

private final KeyFactory keyFactory;
private final ImmutableSetMultimap<Key, ContributionBinding> bindings;
private final ImmutableSetMultimap<Key, DelegateDeclaration> delegates;
Expand Down Expand Up @@ -320,14 +317,14 @@ private static TypeName unwrapMultibindingTypeName(TypeName typeName) {
return ParameterizedTypeName.get(
mapTypeName.rawType,
mapKeyTypeName,
unwrapFrameworkTypeName(mapValueTypeName, MAP_FRAMEWORK_TYPENAMES));
unwrapFrameworkTypeName(mapValueTypeName, FrameworkTypes.MAP_VALUE_FRAMEWORK_TYPES));
}
if (isValidSetMultibindingTypeName(typeName)) {
ParameterizedTypeName setTypeName = (ParameterizedTypeName) typeName;
TypeName setValueTypeName = getOnlyElement(setTypeName.typeArguments);
return ParameterizedTypeName.get(
setTypeName.rawType,
unwrapFrameworkTypeName(setValueTypeName, SET_FRAMEWORK_TYPENAMES));
unwrapFrameworkTypeName(setValueTypeName, FrameworkTypes.SET_VALUE_FRAMEWORK_TYPES));
}
return typeName;
}
Expand All @@ -354,7 +351,7 @@ private static boolean isValidSetMultibindingTypeName(TypeName typeName) {
}

private static TypeName unwrapFrameworkTypeName(
TypeName typeName, ImmutableSet<TypeName> frameworkTypeNames) {
TypeName typeName, ImmutableSet<ClassName> frameworkTypeNames) {
if (typeName instanceof ParameterizedTypeName) {
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
if (frameworkTypeNames.contains(parameterizedTypeName.rawType)) {
Expand Down
11 changes: 3 additions & 8 deletions java/dagger/internal/codegen/binding/KeyFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.internal.codegen.base.ContributionType;
import dagger.internal.codegen.base.FrameworkTypes;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.base.OptionalType;
import dagger.internal.codegen.base.RequestKinds;
Expand Down Expand Up @@ -97,10 +98,7 @@ private XType optionalOf(XType type) {

/** Returns {@code Map<KeyType, FrameworkType<ValueType>>}. */
private XType mapOfFrameworkType(XType keyType, ClassName frameworkClassName, XType valueType) {
checkArgument(
MapType.VALID_FRAMEWORK_REQUEST_KINDS.stream()
.map(RequestKinds::frameworkClassName)
.anyMatch(frameworkClassName::equals));
checkArgument(FrameworkTypes.MAP_VALUE_FRAMEWORK_TYPES.contains(frameworkClassName));
return mapOf(
keyType,
processingEnv.getDeclaredType(
Expand Down Expand Up @@ -317,10 +315,7 @@ public Key unwrapMapValueType(Key key) {
* type.
*/
private Key wrapMapValue(Key key, ClassName frameworkClassName) {
checkArgument(
MapType.VALID_FRAMEWORK_REQUEST_KINDS.stream()
.map(RequestKinds::frameworkClassName)
.anyMatch(frameworkClassName::equals));
checkArgument(FrameworkTypes.MAP_VALUE_FRAMEWORK_TYPES.contains(frameworkClassName));
if (MapType.isMap(key)) {
MapType mapType = MapType.from(key);
if (!mapType.isRawType() && !mapType.valuesAreTypeOf(frameworkClassName)) {
Expand Down
8 changes: 0 additions & 8 deletions java/dagger/internal/codegen/binding/SourceFiles.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,6 @@ public final class SourceFiles {
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,
Expand Down
1 change: 1 addition & 0 deletions java/dagger/internal/codegen/javapoet/TypeNames.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ 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 JAKARTA_PROVIDER = ClassName.get("jakarta.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 =
Expand Down
84 changes: 81 additions & 3 deletions java/dagger/internal/codegen/writing/FactoryGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings;
import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames;
import static dagger.internal.codegen.javapoet.TypeNames.factoryOf;
import static dagger.internal.codegen.model.BindingKind.INJECTION;
Expand All @@ -48,12 +49,14 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.base.SourceFileGenerator;
Expand All @@ -73,6 +76,7 @@
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
Expand Down Expand Up @@ -135,7 +139,7 @@ private TypeSpec.Builder factoryBuilder(ContributionBinding binding) {

return factoryBuilder
.addMethod(getMethod(binding, factoryFields))
.addMethod(staticCreateMethod(binding, factoryFields))
.addMethods(staticCreateMethod(binding, factoryFields))
.addMethod(staticProvisionMethod(binding));
}

Expand Down Expand Up @@ -193,10 +197,12 @@ private MethodSpec constructorMethod(FactoryFields factoryFields) {
// Provider<Baz> bazProvider) {
// return new FooModule_ProvidesFooFactory(module, barProvider, bazProvider);
// }
private MethodSpec staticCreateMethod(ContributionBinding binding, FactoryFields factoryFields) {
private ImmutableList<MethodSpec> staticCreateMethod(
ContributionBinding binding, FactoryFields factoryFields) {
// We use a static create method so that generated components can avoid having to refer to the
// generic types of the factory. (Otherwise they may have visibility problems referring to the
// types.)
ImmutableList.Builder<MethodSpec> methodsBuilder = ImmutableList.builder();
MethodSpec.Builder createMethodBuilder =
methodBuilder("create")
.addModifiers(PUBLIC, STATIC)
Expand All @@ -219,8 +225,32 @@ private MethodSpec staticCreateMethod(ContributionBinding binding, FactoryFields
"return new $T($L)",
parameterizedGeneratedTypeNameForBinding(binding),
parameterNames(parameters));
// If any of the parameters take a Dagger Provider type, we also need to make a
// Javax Provider type for backwards compatibility with components generated at
// an older version.
// Eventually, we will need to remove this and break backwards compatibility
// in order to fully cut the Javax dependency.
if (hasDaggerProviderParams(parameters)) {
methodsBuilder.add(javaxCreateMethod(binding, parameters));
}
}
return createMethodBuilder.build();
methodsBuilder.add(createMethodBuilder.build());
return methodsBuilder.build();
}

private MethodSpec javaxCreateMethod(
ContributionBinding binding, ImmutableList<ParameterSpec> parameters) {
ImmutableList<ParameterSpec> remappedParams = remapParamsToJavaxProvider(parameters);
return methodBuilder("create")
.addModifiers(PUBLIC, STATIC)
.returns(parameterizedGeneratedTypeNameForBinding(binding))
.addTypeVariables(bindingTypeElementTypeVariableNames(binding))
.addParameters(remappedParams)
.addStatement(
"return new $T($L)",
parameterizedGeneratedTypeNameForBinding(binding),
wrappedParametersCodeBlock(remappedParams))
.build();
}

// Example 1: Provision binding.
Expand Down Expand Up @@ -379,6 +409,54 @@ private static Optional<TypeName> factoryTypeName(ContributionBinding binding) {
: Optional.of(factoryOf(providedTypeName(binding)));
}

// Open for sharing with ProducerFactoryGenerator and MembersInjectorGenerator
static boolean hasDaggerProviderParams(List<ParameterSpec> params) {
return params.stream().anyMatch(param -> isDaggerProviderType(param.type));
}

// Open for sharing with ProducerFactoryGenerator and MembersInjectorGenerator
// Returns a code block that represents a parameter list where any javax Provider
// types are wrapped in an asDaggerProvider call
static CodeBlock wrappedParametersCodeBlock(List<ParameterSpec> params) {
return makeParametersCodeBlock(
Lists.transform(
params,
input ->
isProviderType(input.type)
? CodeBlock.of(
"$T.asDaggerProvider($N)", TypeNames.DAGGER_PROVIDERS, input)
: CodeBlock.of("$N", input)));
}

// Open for sharing with ProducerFactoryGenerator and MembersInjectorGenerator
static ImmutableList<ParameterSpec> remapParamsToJavaxProvider(List<ParameterSpec> params) {
return params.stream()
.map(param -> ParameterSpec.builder(
remapDaggerProviderToProvider(param.type), param.name).build())
.collect(toImmutableList());
}

private static boolean isDaggerProviderType(TypeName type) {
return type instanceof ParameterizedTypeName
&& ((ParameterizedTypeName) type).rawType.equals(TypeNames.DAGGER_PROVIDER);
}

private static boolean isProviderType(TypeName type) {
return type instanceof ParameterizedTypeName
&& ((ParameterizedTypeName) type).rawType.equals(TypeNames.PROVIDER);
}

private static TypeName remapDaggerProviderToProvider(TypeName type) {
if (type instanceof ParameterizedTypeName) {
ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
if (parameterizedTypeName.rawType.equals(TypeNames.DAGGER_PROVIDER)) {
return ParameterizedTypeName.get(
TypeNames.PROVIDER, parameterizedTypeName.typeArguments.toArray(new TypeName[0]));
}
}
return type;
}

/** Represents the available fields in the generated factory class. */
private static final class FactoryFields {
static FactoryFields create(ContributionBinding binding) {
Expand Down
Loading

0 comments on commit d0b2cc1

Please sign in to comment.