From ee224b6bb2c1601ff138841dc1c98546feb2c896 Mon Sep 17 00:00:00 2001 From: Brad Corso Date: Fri, 22 May 2026 12:11:23 -0700 Subject: [PATCH] Rename `isWildcard` to `isEffectivelyWildcard` and have it check the java representation for wildcards too. The current `isWildcard` method is poorly named because for Kotlin types it only captures the use-site variance. However, in Dagger we almost always care about the "effective" variance, which depends on many other factors like delcaration-site variance. This CL renames `isWildcard` to `isEffectivelyWildcard` and updates the implementation to check the java representation for wildcards. RELNOTES=N/A PiperOrigin-RevId: 919796256 --- .../DuplicateAndroidInjectorsChecker.java | 4 ++-- .../dagger/internal/codegen/base/Keys.java | 4 ++-- .../validation/BindingElementValidator.java | 4 ++-- .../DependencyRequestValidator.java | 4 ++-- .../validation/MembersInjectionValidator.java | 4 ++-- .../validation/MultibindsMethodValidator.java | 8 +++---- .../validation/ProducesMethodValidator.java | 4 ++-- .../internal/codegen/xprocessing/XTypes.java | 22 ++++++++++--------- 8 files changed, 28 insertions(+), 26 deletions(-) diff --git a/dagger-android-processor/main/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java b/dagger-android-processor/main/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java index 4ff299fe9c1..c970aba9202 100644 --- a/dagger-android-processor/main/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java +++ b/dagger-android-processor/main/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java @@ -18,7 +18,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; @@ -130,7 +130,7 @@ private Stream injectorMapDependencies(Binding binding, BindingGraph gr DaggerElements.toXProcessing(requestedBinding.key().type(), processingEnv) .getTypeArguments() .get(1); - if (isWildcard(valueTypeArgument)) { + if (isEffectivelyWildcard(valueTypeArgument)) { return false; } XType valueType = requireInvariantType(valueTypeArgument); diff --git a/dagger-compiler/main/java/dagger/internal/codegen/base/Keys.java b/dagger-compiler/main/java/dagger/internal/codegen/base/Keys.java index ce85029ad3f..26fd7a99d5b 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/base/Keys.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/base/Keys.java @@ -19,8 +19,8 @@ import static dagger.internal.codegen.base.ComponentAnnotation.allComponentAndCreatorAnnotations; import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import androidx.room3.compiler.processing.XAnnotation; @@ -74,7 +74,7 @@ public static boolean isValidImplicitProvisionKey(Optional qualifie // Otherwise the type argument may be a wildcard (or other type), and we can't // resolve that to actual types. for (XTypeArgument arg : type.getTypeArguments()) { - if (isWildcard(arg) || !isDeclared(requireInvariantType(arg))) { + if (isEffectivelyWildcard(arg) || !isDeclared(requireInvariantType(arg))) { return false; } } diff --git a/dagger-compiler/main/java/dagger/internal/codegen/validation/BindingElementValidator.java b/dagger-compiler/main/java/dagger/internal/codegen/validation/BindingElementValidator.java index a08f9cf18c1..0cef2a8ac2f 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/validation/BindingElementValidator.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/validation/BindingElementValidator.java @@ -24,9 +24,9 @@ import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedInjectionType; import static dagger.internal.codegen.binding.MapKeys.getMapKeys; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; import static dagger.internal.codegen.xprocessing.XTypes.isTypeVariable; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import androidx.room3.compiler.codegen.XClassName; @@ -258,7 +258,7 @@ protected final void checkSetValuesType(XType type) { SetType setType = SetType.from(type); if (setType.isRawType()) { report.addError(elementsIntoSetRawSetMessage()); - } else if (isWildcard(setType.elementType())) { + } else if (isEffectivelyWildcard(setType.elementType())) { report.addError(badTypeMessage()); } else { checkSetValueFrameworkType(requireInvariantType(setType.elementType())); diff --git a/dagger-compiler/main/java/dagger/internal/codegen/validation/DependencyRequestValidator.java b/dagger-compiler/main/java/dagger/internal/codegen/validation/DependencyRequestValidator.java index fa8e90c3ee6..a4fb98628ba 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/validation/DependencyRequestValidator.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/validation/DependencyRequestValidator.java @@ -30,9 +30,9 @@ import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import androidx.room3.compiler.codegen.XTypeName; @@ -215,7 +215,7 @@ && isAssistedFactoryType(typeElement)) { } if (MapType.isMap(keyType)) { MapType mapType = MapType.from(keyType); - if (!mapType.isRawType() && !isWildcard(mapType.valueType())) { + if (!mapType.isRawType() && !isEffectivelyWildcard(mapType.valueType())) { XType valueType = requireInvariantType(mapType.valueType()); if (isMapValueFrameworkType(valueType) && isRawParameterizedType(valueType)) { report.addError( diff --git a/dagger-compiler/main/java/dagger/internal/codegen/validation/MembersInjectionValidator.java b/dagger-compiler/main/java/dagger/internal/codegen/validation/MembersInjectionValidator.java index e3347bbdb70..7f70bb506b0 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/validation/MembersInjectionValidator.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/validation/MembersInjectionValidator.java @@ -20,9 +20,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.xprocessing.XTypes.asArray; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import androidx.room3.compiler.processing.XAnnotation; @@ -108,7 +108,7 @@ private void checkMembersInjectedType(ValidationReport.Builder report, XType typ // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables? // This logic is hard to describe. private boolean isResolvableTypeArgument(XTypeArgument typeArgument) { - if (isWildcard(typeArgument)) { + if (isEffectivelyWildcard(typeArgument)) { return false; } XType type = requireInvariantType(typeArgument); diff --git a/dagger-compiler/main/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java b/dagger-compiler/main/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java index 2c5375d1b1b..ec975c282e4 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/validation/MultibindsMethodValidator.java @@ -23,7 +23,7 @@ import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_ABSTRACT; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.NO_EXCEPTIONS; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import androidx.room3.compiler.processing.XMethodElement; @@ -92,10 +92,10 @@ protected void checkType() { private void checkMapType(MapType mapType) { if (mapType.isRawType()) { report.addError(bindingMethods("return type cannot be a raw Map type")); - } else if (isWildcard(mapType.keyType())) { + } else if (isEffectivelyWildcard(mapType.keyType())) { report.addError( bindingMethods("return type cannot use a wildcard as the Map key type.")); - } else if (isWildcard(mapType.valueType())) { + } else if (isEffectivelyWildcard(mapType.valueType())) { report.addError( bindingMethods("return type cannot use a wildcard as the Map value type.")); } else { @@ -112,7 +112,7 @@ private void checkMapType(MapType mapType) { private void checkSetType(SetType setType) { if (setType.isRawType()) { report.addError(bindingMethods("return type cannot be a raw Set type")); - } else if (isWildcard(setType.elementType())) { + } else if (isEffectivelyWildcard(setType.elementType())) { report.addError(bindingMethods("return type cannot use a wildcard as the Set value type.")); } else { XType elementType = requireInvariantType(setType.elementType()); diff --git a/dagger-compiler/main/java/dagger/internal/codegen/validation/ProducesMethodValidator.java b/dagger-compiler/main/java/dagger/internal/codegen/validation/ProducesMethodValidator.java index e0d516e57c7..83aef513700 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/validation/ProducesMethodValidator.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/validation/ProducesMethodValidator.java @@ -21,8 +21,8 @@ import static dagger.internal.codegen.validation.BindingElementValidator.AllowsScoping.NO_SCOPING; import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_CONCRETE; import static dagger.internal.codegen.validation.BindingMethodValidator.ExceptionSuperclass.EXCEPTION; +import static dagger.internal.codegen.xprocessing.XTypes.isEffectivelyWildcard; import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; -import static dagger.internal.codegen.xprocessing.XTypes.isWildcard; import static dagger.internal.codegen.xprocessing.XTypes.requireInvariantType; import androidx.room3.compiler.processing.XMethodElement; @@ -126,7 +126,7 @@ private Optional unwrapListenableFuture(XType type) { return Optional.empty(); } XTypeArgument typeArgument = getOnlyElement(type.getTypeArguments()); - if (isWildcard(typeArgument)) { + if (isEffectivelyWildcard(typeArgument)) { report.addError(badTypeMessage()); return Optional.empty(); } diff --git a/dagger-compiler/main/java/dagger/internal/codegen/xprocessing/XTypes.java b/dagger-compiler/main/java/dagger/internal/codegen/xprocessing/XTypes.java index a6f0bae9dcd..43404b507d2 100644 --- a/dagger-compiler/main/java/dagger/internal/codegen/xprocessing/XTypes.java +++ b/dagger-compiler/main/java/dagger/internal/codegen/xprocessing/XTypes.java @@ -337,26 +337,28 @@ public static boolean isNoType(XType type) { } /** - * Returns {@code true} if the given type is a wildcard type. + * Returns {@code true} if the given type is effectively a wildcard type. * *

In Java, this represents {@code ?} and {@code ? extends/super Foo}. * - *

In Kotlin, this represents {@code *} or {@code out/in Foo}. + *

In Kotlin, this represents explicit (a.k.a. use-site) variance, e.g. {@code *} or + * {@code out/in Foo}, but also includes types with implicit/effective variance, e.g. based on the + * declaration site variance of the original type parameter that this type argument represents. */ - public static boolean isWildcard(XTypeArgument typeArgument) { - return typeArgument.getVariance() != XVariance.INVARIANT; - } - - public static boolean isJavaWildcard(XTypeArgument typeArgument) { + public static boolean isEffectivelyWildcard(XTypeArgument typeArgument) { XProcessingEnv.Backend backend = getProcessingEnv(typeArgument).getBackend(); switch (backend) { case JAVAC: // This is cheaper than creating the XTypeName. return toJavac(typeArgument).getKind() == TypeKind.WILDCARD; case KSP: - return XTypeNames.isJavaWildcard(typeArgument.asTypeName()); + // If the type argument has explicit (i.e. use-site) variance then we can return `true` + // immediately. Otherwise, we need to check the Java representation, which will calculate + // the "effective" variance by considering things like declaration site variance. + return typeArgument.getVariance() != XVariance.INVARIANT + || XTypeNames.isJavaWildcard(typeArgument.asTypeName()); } - throw new AssertionError("Unexpected backend: " + backend); + throw new AssertionError("Unexpected backend: " + backend); } /** Returns {@code true} if the given type is a declared type. */ @@ -576,7 +578,7 @@ public static XType requireInvariantType(XTypeArgument typeArgument) { */ public static void checkNotWildcard(XTypeArgument typeArgument) { checkArgument( - !isWildcard(typeArgument) && !isJavaWildcard(typeArgument), + !isEffectivelyWildcard(typeArgument), "Type argument is a wildcard: %s", typeArgument); }