1111import com .google .inject .Key ;
1212import com .google .inject .TypeLiteral ;
1313import com .google .inject .spi .BindingSourceRestriction ;
14+ import com .google .inject .spi .Dependency ;
15+ import com .google .inject .spi .ElementSource ;
1416import com .google .inject .spi .UntargettedBinding ;
1517import java .lang .reflect .GenericArrayType ;
18+ import java .lang .reflect .Member ;
1619import java .lang .reflect .ParameterizedType ;
1720import java .lang .reflect .Type ;
1821import java .lang .reflect .WildcardType ;
1922import java .util .ArrayList ;
2023import java .util .Formatter ;
2124import java .util .List ;
2225import java .util .Map ;
26+ import java .util .function .BiFunction ;
2327
2428// TODO(b/165344346): Migrate to use suggest hints API.
2529/** Helper class to find hints for {@link MissingImplementationError}. */
@@ -33,6 +37,15 @@ private MissingImplementationErrorHints() {}
3337 /** When a binding is not found, show at most this many bindings that have some similarities */
3438 private static final int MAX_RELATED_TYPES_REPORTED = 3 ;
3539
40+ private static final String WILDCARD_EXTENDS = "? extends " ;
41+ private static final String WILDCARD_SUPER = "? super " ;
42+
43+ private static final String WILDCARDS_WARNING =
44+ "\n You might be running into a @JvmSuppressWildcards or @JvmWildcards issue." ;
45+
46+ private static final String WILDCARDS_POSSIBLE_FIXES =
47+ "\n Consider these options instead (these are guesses but use your best judgment):" ;
48+
3649 /**
3750 * If the key is unknown and it is one of these types, it generally means there is a missing
3851 * annotation.
@@ -52,13 +65,23 @@ private MissingImplementationErrorHints() {}
5265 * generic arguments (e.g. Optional<String> won't be similar to Optional<Integer>).
5366 */
5467 static boolean areSimilarLookingTypes (Type a , Type b ) {
68+ return areSimilarTypes (
69+ a , b , (aClass , bClass ) -> aClass .getSimpleName ().equals (bClass .getSimpleName ()));
70+ }
71+
72+ private static boolean areOnlyDifferencesInVariance (Type a , Type b ) {
73+ return areSimilarTypes (a , b , Object ::equals );
74+ }
75+
76+ private static boolean areSimilarTypes (
77+ Type a , Type b , BiFunction <Class <?>, Class <?>, Boolean > classSimilarityChecker ) {
5578 if (a instanceof Class && b instanceof Class ) {
56- return ((Class ) a ). getSimpleName (). equals ((( Class ) b ). getSimpleName () );
79+ return classSimilarityChecker . apply ((Class <?> ) a , ( Class <?> ) b );
5780 }
5881 if (a instanceof ParameterizedType && b instanceof ParameterizedType ) {
5982 ParameterizedType aType = (ParameterizedType ) a ;
6083 ParameterizedType bType = (ParameterizedType ) b ;
61- if (!areSimilarLookingTypes (aType .getRawType (), bType .getRawType ())) {
84+ if (!areSimilarTypes (aType .getRawType (), bType .getRawType (), classSimilarityChecker )) {
6285 return false ;
6386 }
6487 Type [] aArgs = aType .getActualTypeArguments ();
@@ -67,7 +90,7 @@ static boolean areSimilarLookingTypes(Type a, Type b) {
6790 return false ;
6891 }
6992 for (int i = 0 ; i < aArgs .length ; i ++) {
70- if (!areSimilarLookingTypes (aArgs [i ], bArgs [i ])) {
93+ if (!areSimilarTypes (aArgs [i ], bArgs [i ], classSimilarityChecker )) {
7194 return false ;
7295 }
7396 }
@@ -76,8 +99,8 @@ static boolean areSimilarLookingTypes(Type a, Type b) {
7699 if (a instanceof GenericArrayType && b instanceof GenericArrayType ) {
77100 GenericArrayType aType = (GenericArrayType ) a ;
78101 GenericArrayType bType = (GenericArrayType ) b ;
79- return areSimilarLookingTypes (
80- aType .getGenericComponentType (), bType .getGenericComponentType ());
102+ return areSimilarTypes (
103+ aType .getGenericComponentType (), bType .getGenericComponentType (), classSimilarityChecker );
81104 }
82105 if (a instanceof WildcardType && b instanceof WildcardType ) {
83106 WildcardType aType = (WildcardType ) a ;
@@ -88,7 +111,7 @@ static boolean areSimilarLookingTypes(Type a, Type b) {
88111 return false ;
89112 }
90113 for (int i = 0 ; i < aLowerBounds .length ; i ++) {
91- if (!areSimilarLookingTypes (aLowerBounds [i ], bLowerBounds [i ])) {
114+ if (!areSimilarTypes (aLowerBounds [i ], bLowerBounds [i ], classSimilarityChecker )) {
92115 return false ;
93116 }
94117 }
@@ -98,7 +121,7 @@ static boolean areSimilarLookingTypes(Type a, Type b) {
98121 return false ;
99122 }
100123 for (int i = 0 ; i < aUpperBounds .length ; i ++) {
101- if (!areSimilarLookingTypes (aUpperBounds [i ], bUpperBounds [i ])) {
124+ if (!areSimilarTypes (aUpperBounds [i ], bUpperBounds [i ], classSimilarityChecker )) {
102125 return false ;
103126 }
104127 }
@@ -113,53 +136,165 @@ static boolean areSimilarLookingTypes(Type a, Type b) {
113136 Type [] lowerBounds = wildcardType .getLowerBounds ();
114137 if (upperBounds .length == 1 && lowerBounds .length == 0 ) {
115138 // This is the '? extends Foo' case
116- return areSimilarLookingTypes (upperBounds [0 ], otherType );
139+ return areSimilarTypes (upperBounds [0 ], otherType , classSimilarityChecker );
117140 }
118141 if (lowerBounds .length == 1
119142 && upperBounds .length == 1
120143 && upperBounds [0 ].equals (Object .class )) {
121144 // this is the '? super Foo' case
122- return areSimilarLookingTypes (lowerBounds [0 ], otherType );
145+ return areSimilarTypes (lowerBounds [0 ], otherType , classSimilarityChecker );
123146 }
124147 }
125148 return false ;
126149 }
127150
128- static <T > ImmutableList <String > getSuggestions (Key <T > key , Injector injector ) {
151+ /**
152+ * Conceptually, this method converts aType to be like bType by adding
153+ * appropriate @JvmSuppressWildcards or @JvmWildcards annotations. This assumes that the two types
154+ * only differ in variance (e.g. `Foo` vs `? extends Foo`) and that aType is implemented in
155+ * Kotlin. For example, if this method is called with the (List<? super String>,
156+ * List<String>), it will return "List<@JvmSuppressWildcards String>".
157+ */
158+ static String convertToLatterViaJvmAnnotations (TypeLiteral <?> aType , TypeLiteral <?> bType ) {
159+ String a = aType .toString ();
160+ String b = bType .toString ();
161+ int j = 0 ;
162+ StringBuilder conversion = new StringBuilder ();
163+ for (int i = 0 ; i < a .length () && j < b .length (); i ++, j ++) {
164+ if (a .charAt (i ) != b .charAt (j )) {
165+ if (a .startsWith (WILDCARD_EXTENDS , i )) {
166+ conversion .append ("@JvmSuppressWildcards " );
167+ i += WILDCARD_EXTENDS .length (); // Skip over the "? extends " part
168+ } else if (a .startsWith (WILDCARD_SUPER , i )) {
169+ conversion .append ("@JvmSuppressWildcards " );
170+ i += WILDCARD_SUPER .length (); // Skip over the "? super " part
171+ } else if (b .startsWith (WILDCARD_EXTENDS , j )) {
172+ conversion .append ("@JvmWildcards " );
173+ j += WILDCARD_EXTENDS .length (); // Skip over the "? extends " part
174+ } else if (b .startsWith (WILDCARD_SUPER , j )) {
175+ conversion .append ("@JvmWildcards " );
176+ j += WILDCARD_SUPER .length (); // Skip over the "? super " part
177+ }
178+ }
179+ conversion .append (a .charAt (i ));
180+ }
181+
182+ // convert any remaining wildcard types to the Kotlin-equivalent.
183+ return conversion .toString ().replaceAll ("\\ ? extends " , "out " ).replaceAll ("\\ ? super " , "in " );
184+ }
185+
186+ private static boolean wasBoundInKotlin (Binding <?> binding ) {
187+ if (binding .getSource () instanceof ElementSource ) {
188+ ElementSource elementSource = (ElementSource ) binding .getSource ();
189+ Object declaringSource = elementSource .getDeclaringSource ();
190+ if (declaringSource instanceof Member ) {
191+ Member member = (Member ) declaringSource ;
192+ if (KotlinSupport .getInstance ().isKotlinClass (member .getDeclaringClass ())) {
193+ return true ;
194+ }
195+ }
196+ if (declaringSource instanceof StackTraceElement ) {
197+ StackTraceElement stackTraceElement = (StackTraceElement ) declaringSource ;
198+ if (stackTraceElement .getFileName () != null
199+ && stackTraceElement .getFileName ().endsWith (".kt" )) {
200+ return true ;
201+ }
202+ }
203+ }
204+ return false ;
205+ }
206+
207+ private static boolean isInjectionFromKotlin (List <Object > sources ) {
208+ for (Object source : sources ) {
209+ if (!(source instanceof Dependency )) {
210+ continue ;
211+ }
212+ Dependency <?> dependency = (Dependency <?>) source ;
213+ if (KotlinSupport .getInstance ()
214+ .isKotlinClass (dependency .getInjectionPoint ().getMember ().getDeclaringClass ())) {
215+ return true ;
216+ }
217+ }
218+ return false ;
219+ }
220+
221+ static <T > ImmutableList <String > getSuggestions (
222+ Key <T > key , Injector injector , List <Object > sources ) {
129223 ImmutableList .Builder <String > suggestions = ImmutableList .builder ();
130224 TypeLiteral <T > type = key .getTypeLiteral ();
131225
132226 BindingSourceRestriction .getMissingImplementationSuggestion (GuiceInternal .GUICE_INTERNAL , key )
133227 .ifPresent (suggestions ::add );
134228
135- // Keys which have similar strings as the desired key
136- List <String > possibleMatches = new ArrayList <>();
137- ImmutableList <Binding <?>> similarTypes =
229+ boolean injectionFromKotlin = isInjectionFromKotlin (sources );
230+
231+ // Keys which have a similar appearance as the desired key
232+ ImmutableList <Binding <?>> similarBindings =
138233 injector .getAllBindings ().values ().stream ()
139234 .filter (b -> !(b instanceof UntargettedBinding )) // These aren't valid matches
140235 .filter (
141236 b -> areSimilarLookingTypes (b .getKey ().getTypeLiteral ().getType (), type .getType ()))
142237 .collect (toImmutableList ());
143- if (!similarTypes .isEmpty ()) {
238+
239+ // Bindings which differ only in variance.
240+ ImmutableList <Binding <?>> wildcardSuggestionBindings =
241+ similarBindings .stream ()
242+ .filter (
243+ b ->
244+ (injectionFromKotlin || wasBoundInKotlin (b ))
245+ && areOnlyDifferencesInVariance (
246+ b .getKey ().getTypeLiteral ().getType (), type .getType ()))
247+ .collect (toImmutableList ());
248+
249+ if (!wildcardSuggestionBindings .isEmpty ()) {
250+ suggestions .add (WILDCARDS_WARNING );
251+ suggestions .add (WILDCARDS_POSSIBLE_FIXES );
252+ for (Binding <?> wildcardSuggestionBinding : wildcardSuggestionBindings ) {
253+ TypeLiteral <?> similarType = wildcardSuggestionBinding .getKey ().getTypeLiteral ();
254+
255+ if (injectionFromKotlin ) {
256+ suggestions .add (
257+ "\n * Inject this: " + convertToLatterViaJvmAnnotations (type , similarType ));
258+ }
259+ if (wasBoundInKotlin (wildcardSuggestionBinding )) {
260+ suggestions
261+ .add ("\n * " )
262+ .add (injectionFromKotlin ? "Or bind this: " : "Bind this: " )
263+ .add (
264+ Messages .format (
265+ "%s %s" ,
266+ convertToLatterViaJvmAnnotations (similarType , type ),
267+ formatVarianceSuggestion (wildcardSuggestionBinding )));
268+ }
269+ }
270+ }
271+
272+ similarBindings =
273+ similarBindings .stream ()
274+ .filter (b -> !wildcardSuggestionBindings .contains (b ))
275+ .collect (toImmutableList ());
276+
277+ List <String > possibleMatches = new ArrayList <>();
278+ if (!similarBindings .isEmpty ()) {
144279 suggestions .add ("\n Did you mean?" );
145- int howMany = min (similarTypes .size (), MAX_MATCHING_TYPES_REPORTED );
280+ int howMany = min (similarBindings .size (), MAX_MATCHING_TYPES_REPORTED );
146281 for (int i = 0 ; i < howMany ; ++i ) {
147- Key <?> bindingKey = similarTypes .get (i ).getKey ();
282+ Key <?> bindingKey = similarBindings .get (i ).getKey ();
148283 // TODO: Look into a better way to prioritize suggestions. For example, possibly
149284 // use levenshtein distance of the given annotation vs actual annotation.
150285 suggestions .add (
151286 Messages .format (
152287 "\n * %s" ,
153288 formatSuggestion (bindingKey , injector .getExistingBinding (bindingKey ))));
154289 }
155- int remaining = similarTypes .size () - MAX_MATCHING_TYPES_REPORTED ;
290+ int remaining = similarBindings .size () - MAX_MATCHING_TYPES_REPORTED ;
156291 if (remaining > 0 ) {
157292 String plural = (remaining == 1 ) ? "" : "s" ;
158293 suggestions .add (
159294 Messages .format (
160295 "\n * %d more binding%s with other annotations." , remaining , plural ));
161296 }
162- } else {
297+ } else if ( wildcardSuggestionBindings . isEmpty ()) {
163298 // For now, do a simple substring search for possibilities. This can help spot
164299 // issues when there are generics being used (such as a wrapper class) and the
165300 // user has forgotten they need to bind based on the wrapper, not the underlying
@@ -196,8 +331,9 @@ static <T> ImmutableList<String> getSuggestions(Key<T> key, Injector injector) {
196331
197332 // If where are no possibilities to suggest, then handle the case of missing
198333 // annotations on simple types. This is usually a bad idea.
199- if (similarTypes .isEmpty ()
334+ if (similarBindings .isEmpty ()
200335 && possibleMatches .isEmpty ()
336+ && wildcardSuggestionBindings .isEmpty ()
201337 && key .getAnnotationType () == null
202338 && COMMON_AMBIGUOUS_TYPES .contains (key .getTypeLiteral ().getRawType ())) {
203339 // We don't recommend using such simple types without annotations.
@@ -213,4 +349,10 @@ private static String formatSuggestion(Key<?> key, Binding<?> binding) {
213349 new SourceFormatter (binding .getSource (), fmt , /* omitPreposition= */ false ).format ();
214350 return fmt .toString ();
215351 }
352+
353+ private static String formatVarianceSuggestion (Binding <?> binding ) {
354+ Formatter fmt = new Formatter ();
355+ new SourceFormatter (binding .getSource (), fmt , /* omitPreposition= */ false ).format ();
356+ return fmt .toString ();
357+ }
216358}
0 commit comments