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