1 /*
<lambda>null2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.model.psi
18 
19 import com.android.tools.lint.checks.infrastructure.TestFile
20 import com.android.tools.metalava.Compatibility
21 import com.android.tools.metalava.DriverTest
22 import com.android.tools.metalava.compatibility
23 import com.android.tools.metalava.libcoreNonNullSource
24 import com.android.tools.metalava.libcoreNullableSource
25 import com.android.tools.metalava.model.AnnotationItem
26 import com.android.tools.metalava.model.Item
27 import com.android.tools.metalava.nonNullSource
28 import com.android.tools.metalava.nullableSource
29 import com.android.tools.metalava.parseSources
30 import com.intellij.psi.JavaRecursiveElementVisitor
31 import com.intellij.psi.PsiAnnotation
32 import com.intellij.psi.PsiType
33 import com.intellij.psi.PsiTypeElement
34 import org.jetbrains.uast.UAnnotation
35 import org.jetbrains.uast.UMethod
36 import org.jetbrains.uast.UTypeReferenceExpression
37 import org.jetbrains.uast.UVariable
38 import org.jetbrains.uast.toUElement
39 import org.jetbrains.uast.visitor.AbstractUastVisitor
40 import org.junit.Assert.assertEquals
41 import org.junit.Test
42 import java.io.File
43 import java.io.PrintWriter
44 import java.io.StringWriter
45 import java.util.function.Predicate
46 
47 class PsiTypePrinterTest : DriverTest() {
48     @Test
49     fun `Test class reference types`() {
50         assertEquals(
51             """
52             Type: PsiClassReferenceType
53             Canonical: java.lang.String
54             Printed: java.lang.String!
55 
56             Type: PsiClassReferenceType
57             Canonical: java.util.List<java.lang.String>
58             Merged: [@Nullable]
59             Printed: java.util.List<java.lang.String!>?
60 
61             Type: PsiClassReferenceType
62             Canonical: java.util.List<java.lang.String>
63             Annotated: java.util.@Nullable List<java.lang.@Nullable String>
64             Printed: java.util.List<java.lang.String?>?
65 
66             Type: PsiClassReferenceType
67             Canonical: java.util.List<java.lang.String>
68             Annotated: java.util.@NonNull List<java.lang.@Nullable String>
69             Printed: java.util.List<java.lang.String?>
70 
71             Type: PsiClassReferenceType
72             Canonical: java.util.List<java.lang.String>
73             Annotated: java.util.@Nullable List<java.lang.@NonNull String>
74             Printed: java.util.List<java.lang.String>?
75 
76             Type: PsiClassReferenceType
77             Canonical: java.util.List<java.lang.String>
78             Annotated: java.util.@NonNull List<java.lang.@NonNull String>
79             Printed: java.util.List<java.lang.String>
80 
81             Type: PsiClassReferenceType
82             Canonical: java.util.Map<java.lang.String,java.lang.Number>
83             Printed: java.util.Map<java.lang.String!,java.lang.Number!>!
84 
85             Type: PsiClassReferenceType
86             Canonical: java.lang.Number
87             Printed: java.lang.Number!
88             """.trimIndent(),
89             prettyPrintTypes(
90                 supportTypeUseAnnotations = true,
91                 kotlinStyleNulls = true,
92                 skip = setOf("int", "long"),
93                 files = listOf(
94                     java(
95                         """
96                         package test.pkg;
97                         import java.util.List;
98                         import java.util.Map;
99 
100                         @SuppressWarnings("ALL")
101                         public class MyClass extends Object {
102                            public String myPlatformField1;
103                            public List<String> getList(Map<String, Number> keys) { return null; }
104 
105                            public @androidx.annotation.Nullable String myNullableField;
106                            public @androidx.annotation.Nullable List<String> myNullableFieldWithPlatformElement;
107 
108                            // Type use annotations
109                            public java.util.@libcore.util.Nullable List<java.lang.@libcore.util.Nullable String> myNullableFieldWithNullableElement;
110                            public java.util.@libcore.util.NonNull List<java.lang.@libcore.util.Nullable String> myNonNullFieldWithNullableElement;
111                            public java.util.@libcore.util.Nullable List<java.lang.@libcore.util.NonNull String> myNullableFieldWithNonNullElement;
112                            public java.util.@libcore.util.NonNull List<java.lang.@libcore.util.NonNull String> myNonNullFieldWithNonNullElement;
113                         }
114                         """
115                     ),
116                     nullableSource,
117                     nonNullSource,
118                     libcoreNonNullSource, // allows TYPE_USE
119                     libcoreNullableSource
120                 )
121             ).trimIndent()
122         )
123     }
124 
125     @Test
126     fun `Test class reference types without Kotlin style nulls`() {
127         assertEquals(
128             """
129             Type: PsiClassReferenceType
130             Canonical: java.util.List<java.lang.String>
131             Annotated: java.util.@Nullable List<java.lang.@Nullable String>
132             Printed: java.util.@libcore.util.Nullable List<java.lang.@libcore.util.Nullable String>
133 
134             Type: PsiClassReferenceType
135             Canonical: java.util.List<java.lang.String>
136             Annotated: java.util.@NonNull List<java.lang.@Nullable String>
137             Printed: java.util.@libcore.util.NonNull List<java.lang.@libcore.util.Nullable String>
138 
139             Type: PsiClassReferenceType
140             Canonical: java.util.List<java.lang.String>
141             Annotated: java.util.@Nullable List<java.lang.@NonNull String>
142             Printed: java.util.@libcore.util.Nullable List<java.lang.@libcore.util.NonNull String>
143 
144             Type: PsiClassReferenceType
145             Canonical: java.util.List<java.lang.String>
146             Annotated: java.util.@NonNull List<java.lang.@NonNull String>
147             Printed: java.util.@libcore.util.NonNull List<java.lang.@libcore.util.NonNull String>
148             """.trimIndent(),
149             prettyPrintTypes(
150                 supportTypeUseAnnotations = true,
151                 kotlinStyleNulls = false,
152                 skip = setOf("int", "long"),
153                 files = listOf(
154                     java(
155                         """
156                         package test.pkg;
157                         import java.util.List;
158                         import java.util.Map;
159 
160                         @SuppressWarnings("ALL")
161                         public class MyClass extends Object {
162                            public java.util.@libcore.util.Nullable List<java.lang.@libcore.util.Nullable String> myNullableFieldWithNullableElement;
163                            public java.util.@libcore.util.NonNull List<java.lang.@libcore.util.Nullable String> myNonNullFieldWithNullableElement;
164                            public java.util.@libcore.util.Nullable List<java.lang.@libcore.util.NonNull String> myNullableFieldWithNonNullElement;
165                            public java.util.@libcore.util.NonNull List<java.lang.@libcore.util.NonNull String> myNonNullFieldWithNonNullElement;
166                         }
167                         """
168                     ),
169                     nullableSource,
170                     nonNullSource,
171                     libcoreNonNullSource, // allows TYPE_USE
172                     libcoreNullableSource
173                 )
174             ).trimIndent()
175         )
176     }
177 
178     @Test
179     fun `Test merge annotations`() {
180         assertEquals(
181             """
182             Type: PsiClassReferenceType
183             Canonical: java.lang.String
184             Merged: [@Nullable]
185             Printed: java.lang.String?
186 
187             Type: PsiArrayType
188             Canonical: java.lang.String[]
189             Merged: [@Nullable]
190             Printed: java.lang.String![]?
191 
192             Type: PsiClassReferenceType
193             Canonical: java.util.List<java.lang.String>
194             Merged: [@Nullable]
195             Printed: java.util.List<java.lang.String!>?
196 
197             Type: PsiClassReferenceType
198             Canonical: java.util.Map<java.lang.String,java.lang.Number>
199             Merged: [@Nullable]
200             Printed: java.util.Map<java.lang.String!,java.lang.Number!>?
201 
202             Type: PsiClassReferenceType
203             Canonical: java.lang.Number
204             Merged: [@Nullable]
205             Printed: java.lang.Number?
206 
207             Type: PsiEllipsisType
208             Canonical: java.lang.String...
209             Merged: [@Nullable]
210             Printed: java.lang.String!...
211             """.trimIndent(),
212             prettyPrintTypes(
213                 supportTypeUseAnnotations = true,
214                 kotlinStyleNulls = true,
215                 skip = setOf("int", "long", "void"),
216                 extraAnnotations = listOf("@libcore.util.Nullable"),
217                 files = listOf(
218                     java(
219                         """
220                         package test.pkg;
221                         import java.util.List;
222                         import java.util.Map;
223 
224                         @SuppressWarnings("ALL")
225                         public class MyClass extends Object {
226                            public String myPlatformField1;
227                            public String[] myPlatformField2;
228                            public List<String> getList(Map<String, Number> keys) { return null; }
229                            public void method(Number number) { }
230                            public void ellipsis(String... args) { }
231                         }
232                         """
233                     ),
234                     nullableSource,
235                     nonNullSource,
236                     libcoreNonNullSource,
237                     libcoreNullableSource
238                 )
239             ).trimIndent()
240         )
241     }
242 
243     @Test
244     fun `Check other annotations than nullness annotations`() {
245         assertEquals(
246             """
247             Type: PsiClassReferenceType
248             Canonical: java.util.List<java.lang.Integer>
249             Annotated: java.util.List<java.lang.@IntRange(from=5,to=10) Integer>
250             Printed: java.util.List<java.lang.@androidx.annotation.IntRange(from=5,to=10) Integer!>!
251             """.trimIndent(),
252             prettyPrintTypes(
253                 supportTypeUseAnnotations = true,
254                 kotlinStyleNulls = true,
255                 files = listOf(
256                     java(
257                         """
258                         package test.pkg;
259                         import java.util.List;
260                         import java.util.Map;
261 
262                         @SuppressWarnings("ALL")
263                         public class MyClass extends Object {
264                            public List<java.lang.@androidx.annotation.IntRange(from=5,to=10) Integer> myRangeList;
265                         }
266                         """
267                     ),
268                     intRangeAsTypeUse
269                 ),
270                 include = setOf(
271                     "java.lang.Integer",
272                     "java.util.List<java.lang.Integer>"
273                 )
274             ).trimIndent()
275         )
276     }
277 
278     @Test
279     fun `Test negative filtering`() {
280         assertEquals(
281             """
282             Type: PsiClassReferenceType
283             Canonical: java.util.List<java.lang.Integer>
284             Annotated: java.util.List<java.lang.@IntRange(from=5,to=10) Integer>
285             Printed: java.util.List<java.lang.Integer!>!
286             """.trimIndent(),
287             prettyPrintTypes(
288                 supportTypeUseAnnotations = true,
289                 kotlinStyleNulls = true,
290                 files = listOf(
291                     java(
292                         """
293                         package test.pkg;
294                         import java.util.List;
295                         import java.util.Map;
296 
297                         @SuppressWarnings("ALL")
298                         public class MyClass extends Object {
299                            public List<java.lang.@androidx.annotation.IntRange(from=5,to=10) Integer> myRangeList;
300                         }
301                         """
302                     ),
303                     intRangeAsTypeUse
304                 ),
305                 include = setOf(
306                     "java.lang.Integer",
307                     "java.util.List<java.lang.Integer>"
308                 ),
309                 // Remove the annotations via filtering
310                 filter = Predicate { false }
311             ).trimIndent()
312         )
313     }
314 
315     @Test
316     fun `Test positive filtering`() {
317         assertEquals(
318             """
319             Type: PsiClassReferenceType
320             Canonical: java.util.List<java.lang.Integer>
321             Annotated: java.util.List<java.lang.@IntRange(from=5,to=10) Integer>
322             Printed: java.util.List<java.lang.@androidx.annotation.IntRange(from=5,to=10) Integer!>!
323             """.trimIndent(),
324             prettyPrintTypes(
325                 supportTypeUseAnnotations = true,
326                 kotlinStyleNulls = true,
327                 files = listOf(
328                     java(
329                         """
330                         package test.pkg;
331                         import java.util.List;
332                         import java.util.Map;
333 
334                         @SuppressWarnings("ALL")
335                         public class MyClass extends Object {
336                            public List<java.lang.@androidx.annotation.IntRange(from=5,to=10) Integer> myRangeList;
337                         }
338                         """
339                     ),
340                     intRangeAsTypeUse
341                 ),
342                 include = setOf(
343                     "java.lang.Integer",
344                     "java.util.List<java.lang.Integer>"
345                 ),
346                 // Include the annotations via filtering
347                 filter = Predicate { true }
348             ).trimIndent()
349         )
350     }
351 
352     // @Test
353     fun `Test primitives`() {
354         assertEquals(
355             """
356             Type: PsiPrimitiveType
357             Canonical: int
358             Printed: int
359 
360             Type: PsiPrimitiveType
361             Canonical: long
362             Printed: long
363 
364             Type: PsiPrimitiveType
365             Canonical: void
366             Printed: void
367 
368             Type: PsiPrimitiveType
369             Canonical: int
370             Annotated: @IntRange(from=5,to=10) int
371             Printed: @androidx.annotation.IntRange(from=5,to=10) int
372             """.trimIndent(),
373             prettyPrintTypes(
374                 supportTypeUseAnnotations = true,
375                 kotlinStyleNulls = true,
376                 files = listOf(
377                     java(
378                         """
379                         package test.pkg;
380 
381                         @SuppressWarnings("ALL")
382                         public class MyClass extends Object {
383                            public void foo() { }
384                            public int myPrimitiveField;
385                            public long myPrimitiveField2;
386                            public void foo(@androidx.annotation.IntRange(from=5,to=10) int foo) { }
387                         }
388                         """
389                     ),
390                     nullableSource,
391                     nonNullSource,
392                     libcoreNonNullSource, // allows TYPE_USE
393                     libcoreNullableSource,
394                     intRangeAsTypeUse
395                 )
396             ).trimIndent()
397         )
398     }
399 
400     // @Test
401     fun `Test primitives with type use turned off`() {
402         assertEquals(
403             """
404             Type: PsiPrimitiveType
405             Canonical: int
406             Printed: int
407 
408             Type: PsiPrimitiveType
409             Canonical: long
410             Printed: long
411 
412             Type: PsiPrimitiveType
413             Canonical: void
414             Printed: void
415 
416             Type: PsiPrimitiveType
417             Canonical: int
418             Annotated: @IntRange(from=5,to=10) int
419             Printed: int
420             """.trimIndent(),
421             prettyPrintTypes(
422                 supportTypeUseAnnotations = false,
423                 kotlinStyleNulls = true,
424                 files = listOf(
425                     java(
426                         """
427                         package test.pkg;
428 
429                         @SuppressWarnings("ALL")
430                         public class MyClass extends Object {
431                            public void foo() { }
432                            public int myPrimitiveField;
433                            public long myPrimitiveField2;
434                            public void foo(@androidx.annotation.IntRange(from=5,to=10) int foo) { }
435                         }
436                         """
437                     ),
438                     nullableSource,
439                     nonNullSource,
440                     libcoreNonNullSource, // allows TYPE_USE
441                     libcoreNullableSource,
442                     intRangeAsTypeUse
443                 )
444             ).trimIndent()
445         )
446     }
447 
448     @Test
449     fun `Test arrays`() {
450         assertEquals(
451             """
452             Type: PsiArrayType
453             Canonical: java.lang.String[]
454             Printed: java.lang.String![]!
455 
456             Type: PsiArrayType
457             Canonical: java.lang.String[][]
458             Printed: java.lang.String![]![]!
459 
460             Type: PsiArrayType
461             Canonical: java.lang.String[]
462             Annotated: java.lang.@Nullable String @Nullable []
463             Printed: java.lang.String?[]?
464 
465             Type: PsiArrayType
466             Canonical: java.lang.String[]
467             Annotated: java.lang.@NonNull String @Nullable []
468             Printed: java.lang.String[]?
469 
470             Type: PsiArrayType
471             Canonical: java.lang.String[]
472             Annotated: java.lang.@Nullable String @NonNull []
473             Printed: java.lang.String?[]
474             """.trimIndent(),
475             prettyPrintTypes(
476                 supportTypeUseAnnotations = true,
477                 kotlinStyleNulls = true,
478                 files = listOf(
479                     java(
480                         """
481                         package test.pkg;
482 
483                         @SuppressWarnings("ALL")
484                         public class MyClass extends Object {
485                            public String[] myArray1;
486                            public String[][] myArray2;
487                            public java.lang.@libcore.util.Nullable String @libcore.util.Nullable [] array1;
488                            public java.lang.@libcore.util.NonNull String @libcore.util.Nullable [] array2;
489                            public java.lang.@libcore.util.Nullable String @libcore.util.NonNull [] array3;
490                         }
491                         """
492                     ),
493                     libcoreNonNullSource,
494                     libcoreNullableSource
495                 ),
496                 skip = setOf("int", "java.lang.String")
497             ).trimIndent()
498         )
499     }
500 
501     @Test
502     fun `Test ellipsis types`() {
503         assertEquals(
504             """
505             Type: PsiEllipsisType
506             Canonical: java.lang.String...
507             Printed: java.lang.String!...
508 
509             Type: PsiEllipsisType
510             Canonical: java.lang.String...
511             Annotated: java.lang.@Nullable String @NonNull ...
512             Merged: [@NonNull]
513             Printed: java.lang.String?...
514             """.trimIndent(),
515             prettyPrintTypes(
516                 supportTypeUseAnnotations = true,
517                 kotlinStyleNulls = true,
518                 files = listOf(
519                     java(
520                         """
521                         package test.pkg;
522                         import java.util.List;
523                         import java.util.Map;
524 
525                         @SuppressWarnings("ALL")
526                         public class MyClass extends Object {
527                             // Ellipsis type
528                            public void ellipsis1(String... args) { }
529                            public void ellipsis2(java.lang.@libcore.util.Nullable String @libcore.util.NonNull ... args) { }
530                         }
531                         """
532                     ),
533                     libcoreNonNullSource,
534                     libcoreNullableSource
535                 ),
536                 skip = setOf("void", "int", "java.lang.String")
537             ).trimIndent()
538         )
539     }
540 
541     @Test
542     fun `Test wildcard type`() {
543         assertEquals(
544             """
545             Type: PsiClassReferenceType
546             Canonical: T
547             Annotated: @NonNull T
548             Merged: [@NonNull]
549             Printed: T
550 
551             Type: PsiWildcardType
552             Canonical: ? super T
553             Printed: ? super T
554 
555             Type: PsiClassReferenceType
556             Canonical: T
557             Printed: T!
558 
559             Type: PsiClassReferenceType
560             Canonical: java.util.Collection<? extends T>
561             Annotated: java.util.Collection<? extends @Nullable T>
562             Printed: java.util.Collection<? extends T?>!
563 
564             Type: PsiWildcardType
565             Canonical: ? extends T
566             Annotated: ? extends @Nullable T
567             Printed: ? extends T?
568 
569             Type: PsiClassReferenceType
570             Canonical: T
571             Annotated: @Nullable T
572             Merged: [@Nullable]
573             Printed: T?
574             """.trimIndent(),
575             prettyPrintTypes(
576                 supportTypeUseAnnotations = true,
577                 kotlinStyleNulls = true,
578                 files = listOf(
579                     java(
580                         """
581                         package test.pkg;
582                         import java.util.List;
583                         import java.util.Map;
584 
585                         @SuppressWarnings("ALL")
586                         public class MyClass extends Object {
587                             // Intersection type
588                             @libcore.util.NonNull public static <T extends java.lang.String & java.lang.Comparable<? super T>> T foo(@libcore.util.Nullable java.util.Collection<? extends @libcore.util.Nullable T> coll) { return null; }
589                         }
590                         """
591                     ),
592                     libcoreNonNullSource,
593                     libcoreNullableSource
594                 ),
595                 skip = setOf("int")
596             ).trimIndent()
597         )
598     }
599 
600     @Test
601     fun `Test primitives in arrays cannot be null`() {
602         assertEquals(
603             """
604             Type: PsiClassReferenceType
605             Canonical: java.util.List<int[]>
606             Printed: java.util.List<int[]!>!
607 
608             Type: PsiClassReferenceType
609             Canonical: java.util.List<boolean[][]>
610             Printed: java.util.List<boolean[]![]!>!
611             """.trimIndent(),
612             prettyPrintTypes(
613                 supportTypeUseAnnotations = true,
614                 kotlinStyleNulls = true,
615                 files = listOf(
616                     java(
617                         """
618                         package test.pkg;
619                         import java.util.List;
620 
621                         @SuppressWarnings("ALL")
622                         public class MyClass extends Object {
623                             public List<int[]> ints;
624                             public List<boolean[][]> booleans;
625                         }
626                         """
627                     )
628                 ),
629                 skip = setOf("int")
630             ).trimIndent()
631         )
632     }
633 
634     @Test
635     fun `Test kotlin`() {
636         assertEquals(
637             """
638             Type: PsiClassReferenceType
639             Canonical: java.lang.String
640             Merged: [@NonNull]
641             Printed: java.lang.String
642 
643             Type: PsiClassReferenceType
644             Canonical: java.util.Map<java.lang.String,java.lang.String>
645             Merged: [@Nullable]
646             Printed: java.util.Map<java.lang.String,java.lang.String>?
647 
648             Type: PsiPrimitiveType
649             Canonical: void
650             Printed: void
651 
652             Type: PsiPrimitiveType
653             Canonical: int
654             Merged: [@NonNull]
655             Printed: int
656 
657             Type: PsiClassReferenceType
658             Canonical: java.lang.Integer
659             Merged: [@Nullable]
660             Printed: java.lang.Integer?
661 
662             Type: PsiEllipsisType
663             Canonical: java.lang.String...
664             Merged: [@NonNull]
665             Printed: java.lang.String!...
666             """.trimIndent(),
667             prettyPrintTypes(
668                 supportTypeUseAnnotations = true,
669                 kotlinStyleNulls = true,
670                 files = listOf(
671                     kotlin(
672                         """
673                         package test.pkg
674                         class Foo {
675                             val foo1: String = "test1"
676                             val foo2: String? = "test1"
677                             val foo3: MutableMap<String?, String>? = null
678                             fun method1(int: Int = 42,
679                                 int2: Int? = null,
680                                 byte: Int = 2 * 21,
681                                 str: String = "hello " + "world",
682                                 vararg args: String) { }
683                         }
684                         """
685                     )
686                 )
687             ).trimIndent()
688         )
689     }
690 
691     @Test
692     fun `Test inner class references`() {
693         assertEquals(
694             """
695             Type: PsiClassReferenceType
696             Canonical: test.pkg.MyClass.MyInner
697             Printed: test.pkg.MyClass.MyInner!
698             """.trimIndent(),
699             prettyPrintTypes(
700                 supportTypeUseAnnotations = true,
701                 kotlinStyleNulls = true,
702                 files = listOf(
703                     java(
704                         """
705                         package test.pkg;
706                         import java.util.List;
707                         import java.util.Map;
708 
709                         @SuppressWarnings("ALL")
710                         public class MyClass extends Object {
711                            public test.pkg.MyClass.MyInner getObserver() { return null; }
712 
713                            public class MyInner {
714                            }
715                         }
716                         """
717                     )
718                 ),
719                 skip = setOf("void", "int", "java.lang.String")
720             ).trimIndent()
721         )
722     }
723 
724     @Test
725     fun `Test type bounds`() {
726         assertEquals(
727             """
728             Type: PsiClassReferenceType
729             Canonical: java.util.List<? extends java.lang.Number>
730             Printed: java.util.List<? extends java.lang.Number>!
731 
732             Type: PsiWildcardType
733             Canonical: ? extends java.lang.Number
734             Printed: ? extends java.lang.Number
735 
736             Type: PsiClassReferenceType
737             Canonical: java.lang.Number
738             Printed: java.lang.Number!
739 
740             Type: PsiClassReferenceType
741             Canonical: java.util.Map<? extends java.lang.Number,? super java.lang.Number>
742             Printed: java.util.Map<? extends java.lang.Number,? super java.lang.Number>!
743 
744             Type: PsiWildcardType
745             Canonical: ? super java.lang.Number
746             Printed: ? super java.lang.Number
747             """.trimIndent(),
748             prettyPrintTypes(
749                 supportTypeUseAnnotations = true,
750                 kotlinStyleNulls = true,
751                 files = listOf(
752                     java(
753                         """
754                         package test.pkg;
755                         import java.util.List;
756                         import java.util.Map;
757 
758                         @SuppressWarnings("ALL")
759                         public class MyClass extends Object {
760                            public void foo1(List<? extends Number> arg) { }
761                            public void foo2(Map<? extends Number, ? super Number> arg) { }
762                         }
763                         """
764                     )
765                 ),
766                 skip = setOf("void")
767             ).trimIndent()
768         )
769     }
770 
771     data class Entry(
772         val type: PsiType,
773         val elementAnnotations: List<AnnotationItem>?,
774         val canonical: String,
775         val annotated: String,
776         val printed: String
777     )
778 
779     private fun prettyPrintTypes(
780         files: List<TestFile>,
781         filter: Predicate<Item>? = null,
782         kotlinStyleNulls: Boolean = true,
783         supportTypeUseAnnotations: Boolean = true,
784         skip: Set<String> = emptySet(),
785         include: Set<String> = emptySet(),
786         extraAnnotations: List<String> = emptyList()
787     ): String {
788         val dir = createProject(*files.toTypedArray())
789         val sourcePath = listOf(File(dir, "src"))
790 
791         val sourceFiles = mutableListOf<File>()
792         fun addFiles(file: File) {
793             if (file.isFile) {
794                 sourceFiles.add(file)
795             } else {
796                 for (child in file.listFiles()) {
797                     addFiles(child)
798                 }
799             }
800         }
801         addFiles(dir)
802 
803         val classPath = mutableListOf<File>()
804         val classPathProperty: String = System.getProperty("java.class.path")
805         for (path in classPathProperty.split(':')) {
806             val file = File(path)
807             if (file.isFile) {
808                 classPath.add(file)
809             }
810         }
811         classPath.add(getPlatformFile("android.jar"))
812 
813         // TestDriver#check normally sets this for all the other tests
814         compatibility = Compatibility(false)
815         val codebase = parseSources(
816             sourceFiles, "test project",
817             sourcePath = sourcePath, classpath = classPath
818         )
819 
820         val results = LinkedHashMap<String, Entry>()
821         fun handleType(type: PsiType, annotations: List<AnnotationItem> = emptyList()) {
822             val key = type.getCanonicalText(true)
823             if (results.contains(key)) {
824                 return
825             }
826             val canonical = type.getCanonicalText(false)
827             if (skip.contains(key) || skip.contains(canonical)) {
828                 return
829             }
830             if (include.isNotEmpty() && !(include.contains(key) || include.contains(canonical))) {
831                 return
832             }
833 
834             val mapAnnotations = false
835             val printer = PsiTypePrinter(codebase, filter, mapAnnotations, kotlinStyleNulls, supportTypeUseAnnotations)
836 
837             var mergeAnnotations: MutableList<AnnotationItem>? = null
838             if (extraAnnotations.isNotEmpty()) {
839                 val list = mutableListOf<AnnotationItem>()
840                 for (annotation in extraAnnotations) {
841                     list.add(codebase.createAnnotation(annotation))
842                 }
843                 mergeAnnotations = list
844             }
845             if (annotations.isNotEmpty()) {
846                 val list = mutableListOf<AnnotationItem>()
847                 for (annotation in annotations) {
848                     list.add(annotation)
849                 }
850                 if (mergeAnnotations == null) {
851                     mergeAnnotations = list
852                 } else {
853                     mergeAnnotations.addAll(list)
854                 }
855             }
856 
857             val pretty = printer.getAnnotatedCanonicalText(type, mergeAnnotations)
858             results[key] = Entry(type, mergeAnnotations, canonical, key, pretty)
859         }
860 
861         for (unit in codebase.units) {
862             unit.toUElement()?.accept(object : AbstractUastVisitor() {
863                 override fun visitMethod(node: UMethod): Boolean {
864                     handle(node.returnType, node.uAnnotations)
865 
866                     // Visit all the type elements in the method: this helps us pick up
867                     // the type parameter lists for example which contains some interesting
868                     // stuff such as type bounds
869                     val psi = node.sourcePsi
870                     psi?.accept(object : JavaRecursiveElementVisitor() {
871                         override fun visitTypeElement(type: PsiTypeElement) {
872                             handle(type.type, psiAnnotations = type.annotations)
873                             super.visitTypeElement(type)
874                         }
875                     })
876                     return super.visitMethod(node)
877                 }
878 
879                 override fun visitVariable(node: UVariable): Boolean {
880                     handle(node.type, node.uAnnotations)
881                     return super.visitVariable(node)
882                 }
883 
884                 private fun handle(
885                     type: PsiType?,
886                     uastAnnotations: List<UAnnotation> = emptyList(),
887                     psiAnnotations: Array<PsiAnnotation> = emptyArray()
888                 ) {
889                     type ?: return
890 
891                     val annotations = mutableListOf<AnnotationItem>()
892                     for (annotation in uastAnnotations) {
893                         annotations.add(UAnnotationItem.create(codebase, annotation))
894                     }
895                     for (annotation in psiAnnotations) {
896                         annotations.add(PsiAnnotationItem.create(codebase, annotation))
897                     }
898 
899                     handleType(type, annotations)
900                 }
901 
902                 override fun visitTypeReferenceExpression(node: UTypeReferenceExpression): Boolean {
903                     handleType(node.type)
904                     return super.visitTypeReferenceExpression(node)
905                 }
906             })
907         }
908 
909         val writer = StringWriter()
910         val printWriter = PrintWriter(writer)
911 
912         results.keys.forEach { key ->
913             val cleanKey = key.replace("libcore.util.", "").replace("androidx.annotation.", "")
914             val entry = results[key]!!
915             val string = entry.printed
916             val type = entry.type
917             val typeName = type.javaClass.simpleName
918             val canonical = entry.canonical
919             printWriter.printf("Type: %s\n", typeName)
920             printWriter.printf("Canonical: %s\n", canonical)
921             if (cleanKey != canonical) {
922                 printWriter.printf("Annotated: %s\n", cleanKey)
923             }
924             val elementAnnotations = entry.elementAnnotations
925             if (elementAnnotations != null && elementAnnotations.isNotEmpty()) {
926                 printWriter.printf("Merged: %s\n", elementAnnotations.toString()
927                     .replace("androidx.annotation.", "")
928                     .replace("libcore.util.", ""))
929             }
930             printWriter.printf("Printed: %s\n\n", string)
931         }
932 
933         return writer.toString().removeSuffix("\n\n")
934     }
935 
936     // TYPE_USE version of intRangeAnnotationSource
937     private val intRangeAsTypeUse = java(
938         """
939         package androidx.annotation;
940         import java.lang.annotation.*;
941         import static java.lang.annotation.ElementType.*;
942         import static java.lang.annotation.RetentionPolicy.SOURCE;
943         @Retention(SOURCE)
944         @Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE,TYPE_USE})
945         public @interface IntRange {
946             long from() default Long.MIN_VALUE;
947             long to() default Long.MAX_VALUE;
948         }
949         """
950     ).indented()
951 }
952