• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google Inc. All Rights Reserved.
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.google.turbine.processing;
18 
19 import static com.google.common.collect.ImmutableList.toImmutableList;
20 import static com.google.common.collect.ImmutableMap.toImmutableMap;
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 import static java.nio.charset.StandardCharsets.UTF_8;
24 import static java.util.Objects.requireNonNull;
25 import static java.util.stream.Collectors.joining;
26 
27 import com.google.common.base.Joiner;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableMap;
30 import com.google.common.collect.ListMultimap;
31 import com.google.common.collect.Lists;
32 import com.google.common.collect.Multimap;
33 import com.google.common.collect.MultimapBuilder;
34 import com.google.common.collect.ObjectArrays;
35 import com.google.common.collect.Streams;
36 import com.google.turbine.binder.Binder;
37 import com.google.turbine.binder.Binder.BindingResult;
38 import com.google.turbine.binder.ClassPathBinder;
39 import com.google.turbine.binder.bound.TypeBoundClass;
40 import com.google.turbine.binder.env.CompoundEnv;
41 import com.google.turbine.binder.env.Env;
42 import com.google.turbine.binder.env.SimpleEnv;
43 import com.google.turbine.binder.sym.ClassSymbol;
44 import com.google.turbine.parse.Parser;
45 import com.google.turbine.testing.TestClassPaths;
46 import com.google.turbine.tree.Tree.CompUnit;
47 import com.sun.source.util.JavacTask;
48 import com.sun.tools.javac.api.JavacTool;
49 import com.sun.tools.javac.file.JavacFileManager;
50 import com.sun.tools.javac.util.Context;
51 import java.net.URI;
52 import java.util.ArrayDeque;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Deque;
56 import java.util.List;
57 import java.util.Optional;
58 import java.util.concurrent.atomic.AtomicInteger;
59 import javax.lang.model.element.Element;
60 import javax.lang.model.element.ElementKind;
61 import javax.lang.model.element.ExecutableElement;
62 import javax.lang.model.element.TypeElement;
63 import javax.lang.model.element.TypeParameterElement;
64 import javax.lang.model.element.VariableElement;
65 import javax.lang.model.type.DeclaredType;
66 import javax.lang.model.type.TypeKind;
67 import javax.lang.model.type.TypeMirror;
68 import javax.lang.model.type.TypeVariable;
69 import javax.lang.model.type.WildcardType;
70 import javax.lang.model.util.ElementScanner8;
71 import javax.lang.model.util.SimpleTypeVisitor8;
72 import javax.lang.model.util.Types;
73 import javax.tools.JavaFileManager;
74 import javax.tools.JavaFileObject.Kind;
75 import javax.tools.SimpleJavaFileObject;
76 
77 class AbstractTurbineTypesTest {
78 
79   protected static class TypeParameters {
80     protected final Types javacTypes;
81     protected final List<List<TypeMirror>> aGroups;
82     protected final Types turbineTypes;
83     protected final List<List<TypeMirror>> bGroups;
84 
TypeParameters( Types javacTypes, List<List<TypeMirror>> aGroups, Types turbineTypes, List<List<TypeMirror>> bGroups)85     private TypeParameters(
86         Types javacTypes,
87         List<List<TypeMirror>> aGroups,
88         Types turbineTypes,
89         List<List<TypeMirror>> bGroups) {
90       this.javacTypes = javacTypes;
91       this.aGroups = aGroups;
92       this.turbineTypes = turbineTypes;
93       this.bGroups = bGroups;
94     }
95   }
96 
97   protected interface TypeBiFunction<T> {
apply(Types types, TypeMirror a, TypeMirror b)98     T apply(Types types, TypeMirror a, TypeMirror b);
99   }
100 
101   static class TypesBiFunctionInput {
102     final Types types;
103     final TypeMirror lhs;
104     final TypeMirror rhs;
105 
TypesBiFunctionInput(Types types, TypeMirror lhs, TypeMirror rhs)106     TypesBiFunctionInput(Types types, TypeMirror lhs, TypeMirror rhs) {
107       this.types = types;
108       this.lhs = lhs;
109       this.rhs = rhs;
110     }
111 
apply(TypeBiFunction<T> function)112     <T> T apply(TypeBiFunction<T> function) {
113       return function.apply(types, lhs, rhs);
114     }
115 
format(String symbol)116     String format(String symbol) {
117       return String.format("`%s` %s `%s`", lhs, symbol, rhs);
118     }
119   }
120 
binaryParameters()121   protected static Iterable<Object[]> binaryParameters() throws Exception {
122     TypeParameters typeParameters = typeParameters();
123     List<Object[]> params = new ArrayList<>();
124     for (int i = 0; i < typeParameters.aGroups.size(); i++) {
125       List<TypeMirror> ax = typeParameters.aGroups.get(i);
126       List<TypeMirror> bx = typeParameters.bGroups.get(i);
127       Streams.zip(
128               Lists.cartesianProduct(ax, ax).stream(),
129               Lists.cartesianProduct(bx, bx).stream(),
130               (a, b) ->
131                   new Object[] {
132                     a.get(0) + " " + a.get(1),
133                     new TypesBiFunctionInput(typeParameters.javacTypes, a.get(0), a.get(1)),
134                     new TypesBiFunctionInput(typeParameters.turbineTypes, b.get(0), b.get(1)),
135                   })
136           .forEachOrdered(params::add);
137     }
138     return params;
139   }
140 
unaryParameters()141   protected static Iterable<Object[]> unaryParameters() throws Exception {
142     TypeParameters typeParameters = typeParameters();
143     List<Object[]> params = new ArrayList<>();
144     for (int i = 0; i < typeParameters.aGroups.size(); i++) {
145       Streams.zip(
146               typeParameters.aGroups.get(i).stream(),
147               typeParameters.bGroups.get(i).stream(),
148               (a, b) ->
149                   new Object[] {
150                     a.toString(), typeParameters.javacTypes, a, typeParameters.turbineTypes, b,
151                   })
152           .forEachOrdered(params::add);
153     }
154     return params;
155   }
156 
typeParameters()157   protected static TypeParameters typeParameters() throws Exception {
158     String[][] types = {
159       // generics
160       {
161         "Object",
162         "String",
163         "Cloneable",
164         "Serializable",
165         "List",
166         "Set",
167         "ArrayList",
168         "Collection",
169         "List<Object>",
170         "List<Number>",
171         "List<Integer>",
172         "ArrayList<Object>",
173         "ArrayList<Number>",
174         "ArrayList<Integer>",
175       },
176       // wildcards
177       {
178         "Object",
179         "String",
180         "Cloneable",
181         "Serializable",
182         "List",
183         "List<?>",
184         "List<Object>",
185         "List<Number>",
186         "List<Integer>",
187         "List<? extends Object>",
188         "List<? extends Number>",
189         "List<? extends Integer>",
190         "List<? super Object>",
191         "List<? super Number>",
192         "List<? super Integer>",
193       },
194       // arrays
195       {
196         "Object",
197         "String",
198         "Cloneable",
199         "Serializable",
200         "List",
201         "Object[]",
202         "Number[]",
203         "List<Integer>[]",
204         "List<? extends Integer>[]",
205         "long[]",
206         "int[]",
207         "int[][]",
208         "Long[]",
209         "Integer[]",
210         "Integer[][]",
211       },
212       // primitives
213       {
214         "Object",
215         "String",
216         "Cloneable",
217         "Serializable",
218         "List",
219         "int",
220         "char",
221         "byte",
222         "short",
223         "boolean",
224         "long",
225         "float",
226         "double",
227         "Integer",
228         "Character",
229         "Byte",
230         "Short",
231         "Boolean",
232         "Long",
233         "Float",
234         "Double",
235       },
236     };
237 
238     // type annotations
239     List<String> annotatedTypes = new ArrayList<>();
240     annotatedTypes.add("@A int @B []");
241     annotatedTypes.add("@A int");
242     // The string representation of these types changed in JDK 19, see JDK-8281238
243     if (Runtime.version().feature() >= 19) {
244       annotatedTypes.add("@A List<@B Integer>");
245       annotatedTypes.add("@A List");
246       annotatedTypes.add("@A List<@A int @B []>");
247       annotatedTypes.add("Map.@A Entry<@B Integer, @C Number>");
248       annotatedTypes.add("@A List<@B ? extends @C String>");
249       annotatedTypes.add("@A List<@B ? super @C String>");
250     }
251 
252     List<String> files = new ArrayList<>();
253     AtomicInteger idx = new AtomicInteger();
254     for (String[] group :
255         ObjectArrays.<String[]>concat(annotatedTypes.toArray(new String[0]), types)) {
256       StringBuilder sb = new StringBuilder();
257       Joiner.on('\n')
258           .appendTo(
259               sb,
260               "package p;",
261               "import java.util.*;",
262               "import java.io.*;",
263               "import java.lang.annotation.*;",
264               String.format("abstract class Test%s {", idx.getAndIncrement()),
265               Streams.mapWithIndex(
266                       Arrays.stream(group), (x, i) -> String.format("  %s f%d;\n", x, i))
267                   .collect(joining("\n")),
268               "  abstract <T extends Serializable & List<T>> T f();",
269               "  abstract <V extends List<V>> V g();",
270               "  abstract <W extends ArrayList> W h();",
271               "  abstract <X extends Serializable> X i();",
272               "  @Target(ElementType.TYPE_USE) @interface A {}",
273               "  @Target(ElementType.TYPE_USE) @interface B {}",
274               "  @Target(ElementType.TYPE_USE) @interface C {}",
275               "}");
276       String content = sb.toString();
277       files.add(content);
278     }
279     // type hierarchies
280     files.add(
281         Joiner.on('\n')
282             .join(
283                 "import java.util.*;",
284                 "class Hierarchy {", //
285                 "  static class A<T> {",
286                 "    class I {}",
287                 "  }",
288                 "  static class D<T> extends A<T[]> {}",
289                 "  static class E<T> extends A<T> {",
290                 "    class J extends I {}",
291                 "  }",
292                 "  static class F<T> extends A {}",
293                 "  static class G<T> extends A<List<T>> {}",
294                 "  A rawA;",
295                 "  A<Object[]> a1;",
296                 "  A<Number[]> a2;",
297                 "  A<Integer[]> a3;",
298                 "  A<? super Object> a4;",
299                 "  A<? super Number> a5;",
300                 "  A<? super Integer> a6;",
301                 "  A<? extends Object> a7;",
302                 "  A<? extends Number> a8;",
303                 "  A<? extends Integer> a9;",
304                 "  A<List<Integer>> a10;",
305                 "  D<Object> d1;",
306                 "  D<Number> d2;",
307                 "  D<Integer> d3;",
308                 "  A<Object>.I i1;",
309                 "  A<Number>.I i2;",
310                 "  A<Integer>.I i3;",
311                 "  E<Object>.J j1;",
312                 "  E<Number>.J j2;",
313                 "  E<Integer>.J j3;",
314                 "  F<Integer> f1;",
315                 "  F<Number> f2;",
316                 "  F<Object> f3;",
317                 "  G<Integer> g1;",
318                 "  G<Number> g2;",
319                 "}"));
320     // methods
321     files.add(
322         Joiner.on('\n')
323             .join(
324                 "import java.io.*;",
325                 "class Methods {",
326                 " void f() {}",
327                 " void g() {}",
328                 " void f(int x) {}",
329                 " void f(int x, int y) {}",
330                 "  abstract static class I {",
331                 "    abstract int f();",
332                 "    abstract void g() throws IOException;",
333                 "    abstract <T> void h();",
334                 "    abstract <T extends String> T i(T s);",
335                 "  }",
336                 "  abstract static class J {",
337                 "    abstract long f();",
338                 "    abstract void g();",
339                 "    abstract <T> void h();",
340                 "    abstract <T extends Number> T i(T s);",
341                 "  }",
342                 "  class K {",
343                 "    void f(K this, int x) {}",
344                 "    void g(K this) {}",
345                 "    <T extends Enum<T> & Serializable> void h(T t) {}",
346                 "  }",
347                 "  class L {",
348                 "    void f(int x) {}",
349                 "    void g() {}",
350                 "    <E extends Enum<E> & Serializable> void h(E t) {}",
351                 "  }",
352                 "}",
353                 "class M<T extends Enum<T> & Serializable> {",
354                 "  void h(T t) {}",
355                 "}"));
356 
357     // type variable bounds
358     files.add(
359         Joiner.on('\n')
360             .join(
361                 "import java.util.List;",
362                 "class N<X, T extends X> {",
363                 "  void h(T t) {}",
364                 "}",
365                 "class O<X extends Enum<X>, T extends X> {",
366                 "  void h(T t) {}",
367                 "}",
368                 "class P<X extends List<?>, T extends X> {",
369                 "  void h(T t) {}",
370                 "}"));
371 
372     Context context = new Context();
373     JavaFileManager fileManager = new JavacFileManager(context, true, UTF_8);
374     idx.set(0);
375     ImmutableList<SimpleJavaFileObject> compilationUnits =
376         files.stream()
377             .map(
378                 x ->
379                     new SimpleJavaFileObject(
380                         URI.create("file://test" + idx.getAndIncrement() + ".java"), Kind.SOURCE) {
381                       @Override
382                       public CharSequence getCharContent(boolean ignoreEncodingErrors) {
383                         return x;
384                       }
385                     })
386             .collect(toImmutableList());
387     JavacTask task =
388         JavacTool.create()
389             .getTask(
390                 /* out= */ null,
391                 fileManager,
392                 /* diagnosticListener= */ null,
393                 /* options= */ ImmutableList.of(),
394                 /* classes= */ ImmutableList.of(),
395                 compilationUnits);
396 
397     Types javacTypes = task.getTypes();
398     ImmutableMap<String, Element> javacElements =
399         Streams.stream(task.analyze())
400             .collect(toImmutableMap(e -> e.getSimpleName().toString(), x -> x));
401 
402     ImmutableList<CompUnit> units = files.stream().map(Parser::parse).collect(toImmutableList());
403     BindingResult bound =
404         Binder.bind(
405             units,
406             ClassPathBinder.bindClasspath(ImmutableList.of()),
407             TestClassPaths.TURBINE_BOOTCLASSPATH,
408             Optional.empty());
409     Env<ClassSymbol, TypeBoundClass> env =
410         CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv())
411             .append(new SimpleEnv<>(bound.units()));
412     ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli());
413     Types turbineTypes = new TurbineTypes(factory);
414     ImmutableMap<String, Element> turbineElements =
415         bound.units().keySet().stream()
416             .filter(x -> !x.binaryName().contains("$")) // only top level classes
417             .collect(toImmutableMap(x -> x.simpleName(), factory::element));
418 
419     assertThat(javacElements.keySet()).containsExactlyElementsIn(turbineElements.keySet());
420 
421     List<List<TypeMirror>> aGroups = new ArrayList<>();
422     List<List<TypeMirror>> bGroups = new ArrayList<>();
423     for (String name : javacElements.keySet()) {
424 
425       List<TypeMirror> aGroup = new ArrayList<>();
426       List<TypeMirror> bGroup = new ArrayList<>();
427 
428       ListMultimap<String, TypeMirror> javacInputs =
429           MultimapBuilder.linkedHashKeys().arrayListValues().build();
430       javacElements
431           .get(name)
432           .getEnclosedElements()
433           .forEach(e -> getTypes(javacTypes, e, javacInputs));
434 
435       ListMultimap<String, TypeMirror> turbineInputs =
436           MultimapBuilder.linkedHashKeys().arrayListValues().build();
437       /*
438        * requireNonNull is safe because `name` is from `javacElements`, which we checked has the
439        * same keys as `turbineElements`.
440        */
441       requireNonNull(turbineElements.get(name))
442           .getEnclosedElements()
443           .forEach(e -> getTypes(turbineTypes, e, turbineInputs));
444 
445       assertThat(turbineInputs.keySet()).containsExactlyElementsIn(javacInputs.keySet());
446 
447       for (String key : javacInputs.keySet()) {
448         List<TypeMirror> a = javacInputs.get(key);
449         List<TypeMirror> b = turbineInputs.get(key);
450         assertWithMessage(key)
451             .that(b.stream().map(x -> x.getKind() + " " + x).collect(toImmutableList()))
452             .containsExactlyElementsIn(
453                 a.stream().map(x -> x.getKind() + " " + x).collect(toImmutableList()))
454             .inOrder();
455         aGroup.addAll(a);
456         bGroup.addAll(b);
457       }
458       aGroups.add(aGroup);
459       bGroups.add(bGroup);
460     }
461     return new TypeParameters(javacTypes, aGroups, turbineTypes, bGroups);
462   }
463 
464   /**
465    * Discover all types contained in the given element, keyed by their immediate enclosing element.
466    *
467    * <p>This method is executed for both javac and Turbine and is expected to produce the same
468    * results in each case.
469    */
getTypes( Types typeUtils, Element element, Multimap<String, TypeMirror> types)470   private static void getTypes(
471       Types typeUtils, Element element, Multimap<String, TypeMirror> types) {
472     element.accept(
473         new ElementScanner8<Void, Void>() {
474 
475           /**
476            * Returns an element name qualified by all enclosing elements, to allow comparison
477            * between javac and turbine's implementation to group related types.
478            */
479           String key(Element e) {
480             Deque<String> flat = new ArrayDeque<>();
481             while (e != null) {
482               flat.addFirst(e.getSimpleName().toString());
483               if (e.getKind() == ElementKind.PACKAGE) {
484                 break;
485               }
486               e = e.getEnclosingElement();
487             }
488             return Joiner.on('.').join(flat);
489           }
490 
491           void addType(Element e, TypeMirror t) {
492             if (t != null) {
493               types.put(key(e), t);
494               t.accept(
495                   new SimpleTypeVisitor8<Void, Void>() {
496                     @Override
497                     public Void visitDeclared(DeclaredType t, Void aVoid) {
498                       for (TypeMirror a : t.getTypeArguments()) {
499                         a.accept(this, null);
500                       }
501                       return null;
502                     }
503 
504                     @Override
505                     public Void visitWildcard(WildcardType t, Void aVoid) {
506                       types.put(key(e), t);
507                       return null;
508                     }
509 
510                     @Override
511                     public Void visitTypeVariable(TypeVariable t, Void aVoid) {
512                       if (t.getUpperBound() != null) {
513                         types.put(key(e), t.getUpperBound());
514                       }
515                       types.put(String.format("getLowerBound(%s)", key(e)), t.getLowerBound());
516                       return null;
517                     }
518                   },
519                   null);
520             }
521           }
522 
523           void addType(Element e, List<? extends TypeMirror> types) {
524             for (TypeMirror type : types) {
525               addType(e, type);
526             }
527           }
528 
529           @Override
530           public Void visitVariable(VariableElement e, Void unused) {
531             if (e.getSimpleName().toString().contains("this$")) {
532               // enclosing instance parameters
533               return null;
534             }
535             addType(e, e.asType());
536             return super.visitVariable(e, null);
537           }
538 
539           @Override
540           public Void visitType(TypeElement e, Void unused) {
541             addType(e, e.asType());
542             return super.visitType(e, null);
543           }
544 
545           @Override
546           public Void visitExecutable(ExecutableElement e, Void unused) {
547             scan(e.getTypeParameters(), null);
548             scan(e.getParameters(), null);
549             addType(e, e.asType());
550             addType(e, e.getReturnType());
551             TypeMirror receiverType = e.getReceiverType();
552             if (receiverType == null) {
553               // work around a javac bug in JDK < 14, see:
554               // https://bugs.openjdk.java.net/browse/JDK-8222369
555               receiverType = typeUtils.getNoType(TypeKind.NONE);
556             }
557             addType(e, receiverType);
558             addType(e, e.getThrownTypes());
559             return null;
560           }
561 
562           @Override
563           public Void visitTypeParameter(TypeParameterElement e, Void unused) {
564             addType(e, e.asType());
565             addType(e, e.getBounds());
566             return null;
567           }
568         },
569         null);
570   }
571 }
572