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