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