From 74ec25c9213b349f6968092d52165440b207b4ab Mon Sep 17 00:00:00 2001 From: Guice Team Date: Wed, 20 Nov 2024 10:09:28 -0800 Subject: [PATCH] Add Guice hints when dealing with Kotlin declaration site variance. PiperOrigin-RevId: 698431304 --- .../google/inject/internal/KotlinSupport.java | 5 + .../internal/KotlinSupportInterface.java | 3 + .../internal/MissingImplementationError.java | 3 +- .../MissingImplementationErrorHints.java | 180 ++++++++++++++++-- .../inject/errors/ErrorMessageTestUtils.java | 3 +- .../MissingImplementationErrorTest.java | 93 +++++++++ ...ntation_has_unnecessary_extends_clause.txt | 16 +- ...on_has_unnecessary_extends_clause_java.txt | 24 +++ ...mentation_has_unnecessary_super_clause.txt | 16 +- ...tion_has_unnecessary_super_clause_java.txt | 24 +++ ..._implementation_missing_extends_clause.txt | 12 +- ...ementation_missing_extends_clause_java.txt | 24 +++ ...ng_implementation_missing_super_clause.txt | 16 +- ...plementation_missing_super_clause_java.txt | 24 +++ .../internal/SimilarLookingTypesTest.java | 33 ++++ 15 files changed, 429 insertions(+), 47 deletions(-) create mode 100644 core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause_java.txt create mode 100644 core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause_java.txt create mode 100644 core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause_java.txt create mode 100644 core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause_java.txt diff --git a/core/src/com/google/inject/internal/KotlinSupport.java b/core/src/com/google/inject/internal/KotlinSupport.java index a11a799aee..75668985c0 100644 --- a/core/src/com/google/inject/internal/KotlinSupport.java +++ b/core/src/com/google/inject/internal/KotlinSupport.java @@ -74,5 +74,10 @@ public boolean isLocalClass(Class clazz) { public boolean isValueClass(Class clazz) { return false; } + + @Override + public boolean isKotlinClass(Class clazz) { + return false; + } } } diff --git a/core/src/com/google/inject/internal/KotlinSupportInterface.java b/core/src/com/google/inject/internal/KotlinSupportInterface.java index 7d76dc4bd0..7fa517b872 100644 --- a/core/src/com/google/inject/internal/KotlinSupportInterface.java +++ b/core/src/com/google/inject/internal/KotlinSupportInterface.java @@ -33,4 +33,7 @@ public interface KotlinSupportInterface { /** Returns whether the {@code clazz} is a Kotlin value class. */ boolean isValueClass(Class clazz); + + /** Returns whether the {@code clazz} is a Kotlin class. */ + boolean isKotlinClass(Class clazz); } diff --git a/core/src/com/google/inject/internal/MissingImplementationError.java b/core/src/com/google/inject/internal/MissingImplementationError.java index 2e4ab9f379..ef2ed5d816 100644 --- a/core/src/com/google/inject/internal/MissingImplementationError.java +++ b/core/src/com/google/inject/internal/MissingImplementationError.java @@ -24,7 +24,8 @@ public MissingImplementationError(Key key, Injector injector, List so key, // Defer building suggestions until messages are requested, to avoid the work associated // with iterating bindings in scenarios where the exceptions are discarded. - Suppliers.memoize(() -> MissingImplementationErrorHints.getSuggestions(key, injector)), + Suppliers.memoize( + () -> MissingImplementationErrorHints.getSuggestions(key, injector, sources)), sources); } diff --git a/core/src/com/google/inject/internal/MissingImplementationErrorHints.java b/core/src/com/google/inject/internal/MissingImplementationErrorHints.java index 20ddb265db..0d689c098a 100644 --- a/core/src/com/google/inject/internal/MissingImplementationErrorHints.java +++ b/core/src/com/google/inject/internal/MissingImplementationErrorHints.java @@ -11,8 +11,11 @@ import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.google.inject.spi.BindingSourceRestriction; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.ElementSource; import com.google.inject.spi.UntargettedBinding; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Member; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; @@ -20,6 +23,7 @@ import java.util.Formatter; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; // TODO(b/165344346): Migrate to use suggest hints API. /** Helper class to find hints for {@link MissingImplementationError}. */ @@ -33,6 +37,15 @@ private MissingImplementationErrorHints() {} /** When a binding is not found, show at most this many bindings that have some similarities */ private static final int MAX_RELATED_TYPES_REPORTED = 3; + private static final String WILDCARD_EXTENDS = "? extends "; + private static final String WILDCARD_SUPER = "? super "; + + private static final String WILDCARDS_WARNING = + "\nYou might be running into a @JvmSuppressWildcards or @JvmWildcards issue."; + + private static final String WILDCARDS_POSSIBLE_FIXES = + "\nConsider these options instead (these are guesses but use your best judgment):"; + /** * If the key is unknown and it is one of these types, it generally means there is a missing * annotation. @@ -52,13 +65,23 @@ private MissingImplementationErrorHints() {} * generic arguments (e.g. Optional<String> won't be similar to Optional<Integer>). */ static boolean areSimilarLookingTypes(Type a, Type b) { + return areSimilarTypes( + a, b, (aClass, bClass) -> aClass.getSimpleName().equals(bClass.getSimpleName())); + } + + private static boolean areOnlyDifferencesInVariance(Type a, Type b) { + return areSimilarTypes(a, b, Object::equals); + } + + private static boolean areSimilarTypes( + Type a, Type b, BiFunction, Class, Boolean> classSimilarityChecker) { if (a instanceof Class && b instanceof Class) { - return ((Class) a).getSimpleName().equals(((Class) b).getSimpleName()); + return classSimilarityChecker.apply((Class) a, (Class) b); } if (a instanceof ParameterizedType && b instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) a; ParameterizedType bType = (ParameterizedType) b; - if (!areSimilarLookingTypes(aType.getRawType(), bType.getRawType())) { + if (!areSimilarTypes(aType.getRawType(), bType.getRawType(), classSimilarityChecker)) { return false; } Type[] aArgs = aType.getActualTypeArguments(); @@ -67,7 +90,7 @@ static boolean areSimilarLookingTypes(Type a, Type b) { return false; } for (int i = 0; i < aArgs.length; i++) { - if (!areSimilarLookingTypes(aArgs[i], bArgs[i])) { + if (!areSimilarTypes(aArgs[i], bArgs[i], classSimilarityChecker)) { return false; } } @@ -76,8 +99,8 @@ static boolean areSimilarLookingTypes(Type a, Type b) { if (a instanceof GenericArrayType && b instanceof GenericArrayType) { GenericArrayType aType = (GenericArrayType) a; GenericArrayType bType = (GenericArrayType) b; - return areSimilarLookingTypes( - aType.getGenericComponentType(), bType.getGenericComponentType()); + return areSimilarTypes( + aType.getGenericComponentType(), bType.getGenericComponentType(), classSimilarityChecker); } if (a instanceof WildcardType && b instanceof WildcardType) { WildcardType aType = (WildcardType) a; @@ -88,7 +111,7 @@ static boolean areSimilarLookingTypes(Type a, Type b) { return false; } for (int i = 0; i < aLowerBounds.length; i++) { - if (!areSimilarLookingTypes(aLowerBounds[i], bLowerBounds[i])) { + if (!areSimilarTypes(aLowerBounds[i], bLowerBounds[i], classSimilarityChecker)) { return false; } } @@ -98,7 +121,7 @@ static boolean areSimilarLookingTypes(Type a, Type b) { return false; } for (int i = 0; i < aUpperBounds.length; i++) { - if (!areSimilarLookingTypes(aUpperBounds[i], bUpperBounds[i])) { + if (!areSimilarTypes(aUpperBounds[i], bUpperBounds[i], classSimilarityChecker)) { return false; } } @@ -113,38 +136,150 @@ static boolean areSimilarLookingTypes(Type a, Type b) { Type[] lowerBounds = wildcardType.getLowerBounds(); if (upperBounds.length == 1 && lowerBounds.length == 0) { // This is the '? extends Foo' case - return areSimilarLookingTypes(upperBounds[0], otherType); + return areSimilarTypes(upperBounds[0], otherType, classSimilarityChecker); } if (lowerBounds.length == 1 && upperBounds.length == 1 && upperBounds[0].equals(Object.class)) { // this is the '? super Foo' case - return areSimilarLookingTypes(lowerBounds[0], otherType); + return areSimilarTypes(lowerBounds[0], otherType, classSimilarityChecker); } } return false; } - static ImmutableList getSuggestions(Key key, Injector injector) { + /** + * Conceptually, this method converts aType to be like bType by adding + * appropriate @JvmSuppressWildcards or @JvmWildcards annotations. This assumes that the two types + * only differ in variance (e.g. `Foo` vs `? extends Foo`) and that aType is implemented in + * Kotlin. For example, if this method is called with the (List<? super String>, + * List<String>), it will return "List<@JvmSuppressWildcards String>". + */ + static String convertToLatterViaJvmAnnotations(TypeLiteral aType, TypeLiteral bType) { + String a = aType.toString(); + String b = bType.toString(); + int j = 0; + StringBuilder conversion = new StringBuilder(); + for (int i = 0; i < a.length() && j < b.length(); i++, j++) { + if (a.charAt(i) != b.charAt(j)) { + if (a.startsWith(WILDCARD_EXTENDS, i)) { + conversion.append("@JvmSuppressWildcards "); + i += WILDCARD_EXTENDS.length(); // Skip over the "? extends " part + } else if (a.startsWith(WILDCARD_SUPER, i)) { + conversion.append("@JvmSuppressWildcards "); + i += WILDCARD_SUPER.length(); // Skip over the "? super " part + } else if (b.startsWith(WILDCARD_EXTENDS, j)) { + conversion.append("@JvmWildcards "); + j += WILDCARD_EXTENDS.length(); // Skip over the "? extends " part + } else if (b.startsWith(WILDCARD_SUPER, j)) { + conversion.append("@JvmWildcards "); + j += WILDCARD_SUPER.length(); // Skip over the "? super " part + } + } + conversion.append(a.charAt(i)); + } + + // convert any remaining wildcard types to the Kotlin-equivalent. + return conversion.toString().replaceAll("\\? extends ", "out ").replaceAll("\\? super ", "in "); + } + + private static boolean wasBoundInKotlin(Binding binding) { + if (binding.getSource() instanceof ElementSource) { + ElementSource elementSource = (ElementSource) binding.getSource(); + Object declaringSource = elementSource.getDeclaringSource(); + if (declaringSource instanceof Member) { + Member member = (Member) declaringSource; + if (KotlinSupport.getInstance().isKotlinClass(member.getDeclaringClass())) { + return true; + } + } + if (declaringSource instanceof StackTraceElement) { + StackTraceElement stackTraceElement = (StackTraceElement) declaringSource; + if (stackTraceElement.getFileName() != null + && stackTraceElement.getFileName().endsWith(".kt")) { + return true; + } + } + } + return false; + } + + private static boolean isInjectionFromKotlin(List sources) { + for (Object source : sources) { + if (!(source instanceof Dependency)) { + continue; + } + Dependency dependency = (Dependency) source; + if (KotlinSupport.getInstance() + .isKotlinClass(dependency.getInjectionPoint().getMember().getDeclaringClass())) { + return true; + } + } + return false; + } + + static ImmutableList getSuggestions( + Key key, Injector injector, List sources) { ImmutableList.Builder suggestions = ImmutableList.builder(); TypeLiteral type = key.getTypeLiteral(); BindingSourceRestriction.getMissingImplementationSuggestion(GuiceInternal.GUICE_INTERNAL, key) .ifPresent(suggestions::add); - // Keys which have similar strings as the desired key - List possibleMatches = new ArrayList<>(); - ImmutableList> similarTypes = + boolean injectionFromKotlin = isInjectionFromKotlin(sources); + + // Keys which have a similar appearance as the desired key + ImmutableList> similarBindings = injector.getAllBindings().values().stream() .filter(b -> !(b instanceof UntargettedBinding)) // These aren't valid matches .filter( b -> areSimilarLookingTypes(b.getKey().getTypeLiteral().getType(), type.getType())) .collect(toImmutableList()); - if (!similarTypes.isEmpty()) { + + // Bindings which differ only in variance. + ImmutableList> wildcardSuggestionBindings = + similarBindings.stream() + .filter( + b -> + (injectionFromKotlin || wasBoundInKotlin(b)) + && areOnlyDifferencesInVariance( + b.getKey().getTypeLiteral().getType(), type.getType())) + .collect(toImmutableList()); + + if (!wildcardSuggestionBindings.isEmpty()) { + suggestions.add(WILDCARDS_WARNING); + suggestions.add(WILDCARDS_POSSIBLE_FIXES); + for (Binding wildcardSuggestionBinding : wildcardSuggestionBindings) { + TypeLiteral similarType = wildcardSuggestionBinding.getKey().getTypeLiteral(); + + if (injectionFromKotlin) { + suggestions.add( + "\n * Inject this: " + convertToLatterViaJvmAnnotations(type, similarType)); + } + if (wasBoundInKotlin(wildcardSuggestionBinding)) { + suggestions + .add("\n * ") + .add(injectionFromKotlin ? "Or bind this: " : "Bind this: ") + .add( + Messages.format( + "%s %s", + convertToLatterViaJvmAnnotations(similarType, type), + formatVarianceSuggestion(wildcardSuggestionBinding))); + } + } + } + + similarBindings = + similarBindings.stream() + .filter(b -> !wildcardSuggestionBindings.contains(b)) + .collect(toImmutableList()); + + List possibleMatches = new ArrayList<>(); + if (!similarBindings.isEmpty()) { suggestions.add("\nDid you mean?"); - int howMany = min(similarTypes.size(), MAX_MATCHING_TYPES_REPORTED); + int howMany = min(similarBindings.size(), MAX_MATCHING_TYPES_REPORTED); for (int i = 0; i < howMany; ++i) { - Key bindingKey = similarTypes.get(i).getKey(); + Key bindingKey = similarBindings.get(i).getKey(); // TODO: Look into a better way to prioritize suggestions. For example, possibly // use levenshtein distance of the given annotation vs actual annotation. suggestions.add( @@ -152,14 +287,14 @@ static ImmutableList getSuggestions(Key key, Injector injector) { "\n * %s", formatSuggestion(bindingKey, injector.getExistingBinding(bindingKey)))); } - int remaining = similarTypes.size() - MAX_MATCHING_TYPES_REPORTED; + int remaining = similarBindings.size() - MAX_MATCHING_TYPES_REPORTED; if (remaining > 0) { String plural = (remaining == 1) ? "" : "s"; suggestions.add( Messages.format( "\n * %d more binding%s with other annotations.", remaining, plural)); } - } else { + } else if (wildcardSuggestionBindings.isEmpty()) { // For now, do a simple substring search for possibilities. This can help spot // issues when there are generics being used (such as a wrapper class) and the // user has forgotten they need to bind based on the wrapper, not the underlying @@ -196,8 +331,9 @@ static ImmutableList getSuggestions(Key key, Injector injector) { // If where are no possibilities to suggest, then handle the case of missing // annotations on simple types. This is usually a bad idea. - if (similarTypes.isEmpty() + if (similarBindings.isEmpty() && possibleMatches.isEmpty() + && wildcardSuggestionBindings.isEmpty() && key.getAnnotationType() == null && COMMON_AMBIGUOUS_TYPES.contains(key.getTypeLiteral().getRawType())) { // We don't recommend using such simple types without annotations. @@ -213,4 +349,10 @@ private static String formatSuggestion(Key key, Binding binding) { new SourceFormatter(binding.getSource(), fmt, /* omitPreposition= */ false).format(); return fmt.toString(); } + + private static String formatVarianceSuggestion(Binding binding) { + Formatter fmt = new Formatter(); + new SourceFormatter(binding.getSource(), fmt, /* omitPreposition= */ false).format(); + return fmt.toString(); + } } diff --git a/core/test/com/google/inject/errors/ErrorMessageTestUtils.java b/core/test/com/google/inject/errors/ErrorMessageTestUtils.java index df6639ad70..55df2b81ad 100644 --- a/core/test/com/google/inject/errors/ErrorMessageTestUtils.java +++ b/core/test/com/google/inject/errors/ErrorMessageTestUtils.java @@ -47,6 +47,7 @@ private static String getExpectedError(String fileName) throws IOException { ErrorMessageTestUtils.class.getResource( "/core/test/com/google/inject/errors/testdata/" + fileName); } - return Resources.toString(resource, UTF_8); + String expectedError = Resources.toString(resource, UTF_8); + return expectedError; } } diff --git a/core/test/com/google/inject/errors/MissingImplementationErrorTest.java b/core/test/com/google/inject/errors/MissingImplementationErrorTest.java index cff1edbe2a..3c8bb9b531 100644 --- a/core/test/com/google/inject/errors/MissingImplementationErrorTest.java +++ b/core/test/com/google/inject/errors/MissingImplementationErrorTest.java @@ -18,6 +18,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Optional; +import java.util.List; import jakarta.inject.Provider; import jakarta.inject.Qualifier; import org.junit.Before; @@ -232,4 +233,96 @@ public void testMismatchedOptionals() { assertGuiceErrorEqualsIgnoreLineNumber( exception.getMessage(), "missing_implementation_with_mismatched_optionals.txt"); } + + private static final class InjectionMissingExtendsClauseModule extends AbstractModule { + + @Provides + List provideString() { + throw new RuntimeException("not reachable"); + } + + @Provides + Dao provideInteger(List dep) { + throw new RuntimeException("not reachable"); + } + } + + @Test + public void testInjectionMissingExtendsClause() { + CreationException exception = + assertThrows( + CreationException.class, + () -> Guice.createInjector(new InjectionMissingExtendsClauseModule())); + assertGuiceErrorEqualsIgnoreLineNumber( + exception.getMessage(), "missing_implementation_missing_extends_clause_java.txt"); + } + + private static final class InjectionMissingSuperClauseModule extends AbstractModule { + + @Provides + List provideString() { + throw new RuntimeException("not reachable"); + } + + @Provides + Dao provideInteger(List dep) { + throw new RuntimeException("not reachable"); + } + } + + @Test + public void testInjectionMissingSuperClause() { + CreationException exception = + assertThrows( + CreationException.class, + () -> Guice.createInjector(new InjectionMissingSuperClauseModule())); + assertGuiceErrorEqualsIgnoreLineNumber( + exception.getMessage(), "missing_implementation_missing_super_clause_java.txt"); + } + + private static final class InjectionHasUnnecessaryExtendsClauseModule extends AbstractModule { + + @Provides + List provideString() { + throw new RuntimeException("not reachable"); + } + + @Provides + Dao provideInteger(List dep) { + throw new RuntimeException("not reachable"); + } + } + + @Test + public void testInjectionHasUnnecessaryExtendsClause() { + CreationException exception = + assertThrows( + CreationException.class, + () -> Guice.createInjector(new InjectionHasUnnecessaryExtendsClauseModule())); + assertGuiceErrorEqualsIgnoreLineNumber( + exception.getMessage(), "missing_implementation_has_unnecessary_extends_clause_java.txt"); + } + + private static final class InjectionHasUnnecessarySuperClauseModule extends AbstractModule { + + @Provides + List provideString() { + throw new RuntimeException("not reachable"); + } + + @Provides + Dao provideInteger(List dep) { + throw new RuntimeException("not reachable"); + } + } + + @Test + public void testInjectionHasUnnecessarySuperClause() { + CreationException exception = + assertThrows( + CreationException.class, + () -> Guice.createInjector(new InjectionHasUnnecessarySuperClauseModule())); + assertGuiceErrorEqualsIgnoreLineNumber( + exception.getMessage(), "missing_implementation_has_unnecessary_super_clause_java.txt"); + } } diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause.txt index c9007f7501..8eb6f55fa5 100644 --- a/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause.txt +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause.txt @@ -1,14 +1,16 @@ Unable to create injector, see the following errors: -1) [Guice/MissingImplementation]: No implementation for MissingImplementationErrorKtTest$Producer was bound. +1) [Guice/MissingImplementation]: No implementation for Producer was bound. -Did you mean? - * MissingImplementationErrorKtTest$Producer bound at MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule.provideProducerOfFoo(MissingImplementationErrorKtTest.kt:53) +You might be running into a @JvmSuppressWildcards or @JvmWildcards issue. +Consider these options instead (these are guesses but use your best judgment): + * Inject this: Producer<@JvmSuppressWildcards Foo> + * Or bind this: Producer<@JvmWildcards Foo> at MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule.provideProducerOfFoo(MissingImplementationErrorKtTest.kt:52) Requested by: -1 : MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule.injectProducerOfFoo(MissingImplementationErrorKtTest.kt:58) +1 : MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule.injectProducerOfFoo(MissingImplementationErrorKtTest.kt:56) \_ for 1st parameter unused - at MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule.injectProducerOfFoo(MissingImplementationErrorKtTest.kt:58) + at MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule.injectProducerOfFoo(MissingImplementationErrorKtTest.kt:56) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION @@ -18,9 +20,9 @@ Learn more: ====================== Full classname legend: ====================== -MissingImplementationErrorKtTest$Foo: "com.google.inject.errors.MissingImplementationErrorKtTest$Foo" +Foo: "com.google.inject.errors.Foo" MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule: "com.google.inject.errors.MissingImplementationErrorKtTest$InjectionHasUnnecessaryExtendsClauseModule" -MissingImplementationErrorKtTest$Producer: "com.google.inject.errors.MissingImplementationErrorKtTest$Producer" +Producer: "com.google.inject.errors.Producer" ======================== End of classname legend: ======================== diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause_java.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause_java.txt new file mode 100644 index 0000000000..5fb9e1ba83 --- /dev/null +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_extends_clause_java.txt @@ -0,0 +1,24 @@ +Unable to create injector, see the following errors: + +1) [Guice/MissingImplementation]: No implementation for List was bound. + +Did you mean? + * List bound at MissingImplementationErrorTest$InjectionHasUnnecessaryExtendsClauseModule.provideString(MissingImplementationErrorTest.java:293) + +Requested by: +1 : MissingImplementationErrorTest$InjectionHasUnnecessaryExtendsClauseModule.provideInteger(MissingImplementationErrorTest.java:298) + \_ for 1st parameter dep + at MissingImplementationErrorTest$InjectionHasUnnecessaryExtendsClauseModule.provideInteger(MissingImplementationErrorTest.java:298) + +Learn more: + https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION + +1 error + +====================== +Full classname legend: +====================== +MissingImplementationErrorTest$InjectionHasUnnecessaryExtendsClauseModule: "com.google.inject.errors.MissingImplementationErrorTest$InjectionHasUnnecessaryExtendsClauseModule" +======================== +End of classname legend: +======================== diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause.txt index 1dd3903e9d..8ef12d5e46 100644 --- a/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause.txt +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause.txt @@ -1,14 +1,16 @@ Unable to create injector, see the following errors: -1) [Guice/MissingImplementation]: No implementation for MissingImplementationErrorKtTest$Consumer was bound. +1) [Guice/MissingImplementation]: No implementation for Consumer was bound. -Did you mean? - * MissingImplementationErrorKtTest$Consumer bound at MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule.provideConsumerOfFoo(MissingImplementationErrorKtTest.kt:98) +You might be running into a @JvmSuppressWildcards or @JvmWildcards issue. +Consider these options instead (these are guesses but use your best judgment): + * Inject this: Consumer<@JvmSuppressWildcards Foo> + * Or bind this: Consumer<@JvmWildcards Foo> at MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule.provideConsumerOfFoo(MissingImplementationErrorKtTest.kt:95) Requested by: -1 : MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:103) +1 : MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:99) \_ for 1st parameter unused - at MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:103) + at MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:99) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION @@ -18,8 +20,8 @@ Learn more: ====================== Full classname legend: ====================== -MissingImplementationErrorKtTest$Consumer: "com.google.inject.errors.MissingImplementationErrorKtTest$Consumer" -MissingImplementationErrorKtTest$Foo: "com.google.inject.errors.MissingImplementationErrorKtTest$Foo" +Consumer: "com.google.inject.errors.Consumer" +Foo: "com.google.inject.errors.Foo" MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule: "com.google.inject.errors.MissingImplementationErrorKtTest$InjectionHasUnnecessarySuperClauseModule" ======================== End of classname legend: diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause_java.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause_java.txt new file mode 100644 index 0000000000..0404b2d30c --- /dev/null +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_has_unnecessary_super_clause_java.txt @@ -0,0 +1,24 @@ +Unable to create injector, see the following errors: + +1) [Guice/MissingImplementation]: No implementation for List was bound. + +Did you mean? + * List bound at MissingImplementationErrorTest$InjectionHasUnnecessarySuperClauseModule.provideString(MissingImplementationErrorTest.java:318) + +Requested by: +1 : MissingImplementationErrorTest$InjectionHasUnnecessarySuperClauseModule.provideInteger(MissingImplementationErrorTest.java:323) + \_ for 1st parameter dep + at MissingImplementationErrorTest$InjectionHasUnnecessarySuperClauseModule.provideInteger(MissingImplementationErrorTest.java:323) + +Learn more: + https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION + +1 error + +====================== +Full classname legend: +====================== +MissingImplementationErrorTest$InjectionHasUnnecessarySuperClauseModule: "com.google.inject.errors.MissingImplementationErrorTest$InjectionHasUnnecessarySuperClauseModule" +======================== +End of classname legend: +======================== diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause.txt index 19539aadf2..807b46d68e 100644 --- a/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause.txt +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause.txt @@ -1,9 +1,11 @@ Unable to create injector, see the following errors: -1) [Guice/MissingImplementation]: No implementation for MissingImplementationErrorKtTest$Producer was bound. +1) [Guice/MissingImplementation]: No implementation for Producer was bound. -Did you mean? - * MissingImplementationErrorKtTest$Producer bound at MissingImplementationErrorKtTest$InjectionMissingExtendsClauseModule.configure(MissingImplementationErrorKtTest.kt:118) +You might be running into a @JvmSuppressWildcards or @JvmWildcards issue. +Consider these options instead (these are guesses but use your best judgment): + * Inject this: Producer<@JvmWildcards Foo> + * Or bind this: Producer<@JvmSuppressWildcards Foo> at MissingImplementationErrorKtTest$InjectionMissingExtendsClauseModule.configure(MissingImplementationErrorKtTest.kt:114) Requested by: 1 : MissingImplementationErrorKtTest$InjectionMissingExtendsClauseModule.injectProducerOfFoo(MissingImplementationErrorKtTest.kt:36) @@ -18,9 +20,9 @@ Learn more: ====================== Full classname legend: ====================== -MissingImplementationErrorKtTest$Foo: "com.google.inject.errors.MissingImplementationErrorKtTest$Foo" +Foo: "com.google.inject.errors.Foo" MissingImplementationErrorKtTest$InjectionMissingExtendsClauseModule: "com.google.inject.errors.MissingImplementationErrorKtTest$InjectionMissingExtendsClauseModule" -MissingImplementationErrorKtTest$Producer: "com.google.inject.errors.MissingImplementationErrorKtTest$Producer" +Producer: "com.google.inject.errors.Producer" ======================== End of classname legend: ======================== diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause_java.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause_java.txt new file mode 100644 index 0000000000..557120559d --- /dev/null +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_extends_clause_java.txt @@ -0,0 +1,24 @@ +Unable to create injector, see the following errors: + +1) [Guice/MissingImplementation]: No implementation for List was bound. + +Did you mean? + * List bound at MissingImplementationErrorTest$InjectionMissingExtendsClauseModule.provideString(MissingImplementationErrorTest.java:243) + +Requested by: +1 : MissingImplementationErrorTest$InjectionMissingExtendsClauseModule.provideInteger(MissingImplementationErrorTest.java:248) + \_ for 1st parameter dep + at MissingImplementationErrorTest$InjectionMissingExtendsClauseModule.provideInteger(MissingImplementationErrorTest.java:248) + +Learn more: + https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION + +1 error + +====================== +Full classname legend: +====================== +MissingImplementationErrorTest$InjectionMissingExtendsClauseModule: "com.google.inject.errors.MissingImplementationErrorTest$InjectionMissingExtendsClauseModule" +======================== +End of classname legend: +======================== diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause.txt index 1eac3fde57..69bcb8bf56 100644 --- a/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause.txt +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause.txt @@ -1,14 +1,16 @@ Unable to create injector, see the following errors: -1) [Guice/MissingImplementation]: No implementation for MissingImplementationErrorKtTest$Consumer was bound. +1) [Guice/MissingImplementation]: No implementation for Consumer was bound. -Did you mean? - * MissingImplementationErrorKtTest$Consumer bound at MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule.configure(MissingImplementationErrorKtTest.kt:118) +You might be running into a @JvmSuppressWildcards or @JvmWildcards issue. +Consider these options instead (these are guesses but use your best judgment): + * Inject this: Consumer<@JvmWildcards Foo> + * Or bind this: Consumer<@JvmSuppressWildcards Foo> at MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule.configure(MissingImplementationErrorKtTest.kt:114) Requested by: -1 : MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:81) +1 : MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:79) \_ for 1st parameter unused - at MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:81) + at MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule.injectConsumerOfFoo(MissingImplementationErrorKtTest.kt:79) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION @@ -18,8 +20,8 @@ Learn more: ====================== Full classname legend: ====================== -MissingImplementationErrorKtTest$Consumer: "com.google.inject.errors.MissingImplementationErrorKtTest$Consumer" -MissingImplementationErrorKtTest$Foo: "com.google.inject.errors.MissingImplementationErrorKtTest$Foo" +Consumer: "com.google.inject.errors.Consumer" +Foo: "com.google.inject.errors.Foo" MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule: "com.google.inject.errors.MissingImplementationErrorKtTest$InjectionMissingSuperClauseModule" ======================== End of classname legend: diff --git a/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause_java.txt b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause_java.txt new file mode 100644 index 0000000000..22692560cc --- /dev/null +++ b/core/test/com/google/inject/errors/testdata/missing_implementation_missing_super_clause_java.txt @@ -0,0 +1,24 @@ +Unable to create injector, see the following errors: + +1) [Guice/MissingImplementation]: No implementation for List was bound. + +Did you mean? + * List bound at MissingImplementationErrorTest$InjectionMissingSuperClauseModule.provideString(MissingImplementationErrorTest.java:268) + +Requested by: +1 : MissingImplementationErrorTest$InjectionMissingSuperClauseModule.provideInteger(MissingImplementationErrorTest.java:273) + \_ for 1st parameter dep + at MissingImplementationErrorTest$InjectionMissingSuperClauseModule.provideInteger(MissingImplementationErrorTest.java:273) + +Learn more: + https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION + +1 error + +====================== +Full classname legend: +====================== +MissingImplementationErrorTest$InjectionMissingSuperClauseModule: "com.google.inject.errors.MissingImplementationErrorTest$InjectionMissingSuperClauseModule" +======================== +End of classname legend: +======================== diff --git a/core/test/com/google/inject/internal/SimilarLookingTypesTest.java b/core/test/com/google/inject/internal/SimilarLookingTypesTest.java index 0de8150528..4406d437b7 100644 --- a/core/test/com/google/inject/internal/SimilarLookingTypesTest.java +++ b/core/test/com/google/inject/internal/SimilarLookingTypesTest.java @@ -2,9 +2,11 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.inject.internal.MissingImplementationErrorHints.areSimilarLookingTypes; +import static com.google.inject.internal.MissingImplementationErrorHints.convertToLatterViaJvmAnnotations; import com.google.inject.TypeLiteral; import java.util.List; +import java.util.Map; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -136,4 +138,35 @@ public void differentWildcardType_super() { new TypeLiteral>() {}.getType())) .isFalse(); } + + @Test + public void oneTypeConversion() { + assertThat( + convertToLatterViaJvmAnnotations( + new TypeLiteral>() {}, + new TypeLiteral>() {})) + .isEqualTo("java.util.Map<@JvmWildcards java.lang.String, java.lang.Integer>"); + } + + @Test + public void mixedConversion() { + assertThat( + convertToLatterViaJvmAnnotations( + new TypeLiteral>() {}, + new TypeLiteral>() {})) + .isEqualTo( + "java.util.Map<@JvmWildcards java.lang.String," + + " @JvmSuppressWildcards java.lang.Integer>"); + } + + @Test + public void complexConversion() { + assertThat( + convertToLatterViaJvmAnnotations( + new TypeLiteral, ? super Integer>>() {}, + new TypeLiteral, Integer>>() {})) + .isEqualTo( + "java.util.Map<@JvmWildcards java.util.List," + + " @JvmSuppressWildcards java.lang.Integer>"); + } }