TypeHarness.java revision 3552:467ad69d5948
1/* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import java.net.URI; 25import java.util.ArrayList; 26import java.util.function.Consumer; 27import java.util.stream.Collectors; 28 29import javax.tools.JavaFileObject; 30import javax.tools.SimpleJavaFileObject; 31 32import com.sun.tools.javac.code.BoundKind; 33import com.sun.tools.javac.code.Flags; 34import com.sun.tools.javac.util.Context; 35import com.sun.tools.javac.code.Types; 36import com.sun.tools.javac.code.Symtab; 37import com.sun.tools.javac.code.Type; 38import com.sun.tools.javac.code.Type.*; 39import com.sun.tools.javac.code.Symbol.*; 40import com.sun.tools.javac.comp.Attr; 41import com.sun.tools.javac.comp.Check; 42import com.sun.tools.javac.comp.Infer; 43import com.sun.tools.javac.comp.InferenceContext; 44import com.sun.tools.javac.comp.Modules; 45import com.sun.tools.javac.util.List; 46import com.sun.tools.javac.util.ListBuffer; 47import com.sun.tools.javac.util.Name; 48import com.sun.tools.javac.util.Names; 49import com.sun.tools.javac.file.JavacFileManager; 50import com.sun.tools.javac.main.JavaCompiler; 51import com.sun.tools.javac.tree.JCTree.JCVariableDecl; 52import com.sun.tools.javac.util.Abort; 53import com.sun.tools.javac.util.Assert; 54import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; 55 56import static com.sun.tools.javac.util.List.*; 57 58/** 59 * Test harness whose goal is to simplify the task of writing type-system 60 * regression test. It provides functionalities to build custom types as well 61 * as to access the underlying javac's symbol table in order to retrieve 62 * predefined types. Among the features supported by the harness are: type 63 * substitution, type containment, subtyping, cast-conversion, assigment 64 * conversion. 65 * 66 * This class is meant to be a common super class for all concrete type test 67 * classes. A subclass can access the type-factory and the test methods so as 68 * to write compact tests. An example is reported below: 69 * 70 * <pre> 71 * Type X = fac.TypeVariable(); 72 * Type Y = fac.TypeVariable(); 73 * Type A_X_Y = fac.Class(0, X, Y); 74 * Type A_Obj_Obj = fac.Class(0, 75 * predef.objectType, 76 * predef.objectType); 77 * checkSameType(A_Obj_Obj, subst(A_X_Y, 78 * Mapping(X, predef.objectType), 79 * Mapping(Y, predef.objectType))); 80 * </pre> 81 * 82 * The above code is used to create two class types, namely {@code A<X,Y>} and 83 * {@code A<Object,Object>} where both {@code X} and {@code Y} are type-variables. 84 * The code then verifies that {@code [X:=Object,Y:=Object]A<X,Y> == A<Object,Object>}. 85 * 86 * @author mcimadamore 87 * @author vromero 88 */ 89public class TypeHarness { 90 91 protected Types types; 92 protected Check chk; 93 protected Symtab predef; 94 protected Names names; 95 protected ReusableJavaCompiler tool; 96 protected Infer infer; 97 protected Context context; 98 99 protected Factory fac; 100 101 protected TypeHarness() { 102 context = new Context(); 103 JavacFileManager.preRegister(context); 104 MyAttr.preRegister(context); 105 tool = new ReusableJavaCompiler(context); 106 types = Types.instance(context); 107 infer = Infer.instance(context); 108 chk = Check.instance(context); 109 predef = Symtab.instance(context); 110 names = Names.instance(context); 111 fac = new Factory(); 112 } 113 114 // <editor-fold defaultstate="collapsed" desc="type assertions"> 115 116 /** assert that 's' is a subtype of 't' */ 117 public void assertSubtype(Type s, Type t) { 118 assertSubtype(s, t, true); 119 } 120 121 /** assert that 's' is/is not a subtype of 't' */ 122 public void assertSubtype(Type s, Type t, boolean expected) { 123 if (types.isSubtype(s, t) != expected) { 124 String msg = expected ? 125 " is not a subtype of " : 126 " is a subtype of "; 127 error(s + msg + t); 128 } 129 } 130 131 /** assert that 's' is the same type as 't' */ 132 public void assertSameType(Type s, Type t) { 133 assertSameType(s, t, true); 134 } 135 136 /** assert that 's' is/is not the same type as 't' */ 137 public void assertSameType(Type s, Type t, boolean expected) { 138 if (types.isSameType(s, t) != expected) { 139 String msg = expected ? 140 " is not the same type as " : 141 " is the same type as "; 142 error(s + msg + t); 143 } 144 } 145 146 /** assert that 's' is castable to 't' */ 147 public void assertCastable(Type s, Type t) { 148 assertCastable(s, t, true); 149 } 150 151 /** assert that 's' is/is not castable to 't' */ 152 public void assertCastable(Type s, Type t, boolean expected) { 153 if (types.isCastable(s, t) != expected) { 154 String msg = expected ? 155 " is not castable to " : 156 " is castable to "; 157 error(s + msg + t); 158 } 159 } 160 161 /** assert that 's' is convertible (method invocation conversion) to 't' */ 162 public void assertConvertible(Type s, Type t) { 163 assertCastable(s, t, true); 164 } 165 166 /** assert that 's' is/is not convertible (method invocation conversion) to 't' */ 167 public void assertConvertible(Type s, Type t, boolean expected) { 168 if (types.isConvertible(s, t) != expected) { 169 String msg = expected ? 170 " is not convertible to " : 171 " is convertible to "; 172 error(s + msg + t); 173 } 174 } 175 176 /** assert that 's' is assignable to 't' */ 177 public void assertAssignable(Type s, Type t) { 178 assertCastable(s, t, true); 179 } 180 181 /** assert that 's' is/is not assignable to 't' */ 182 public void assertAssignable(Type s, Type t, boolean expected) { 183 if (types.isAssignable(s, t) != expected) { 184 String msg = expected ? 185 " is not assignable to " : 186 " is assignable to "; 187 error(s + msg + t); 188 } 189 } 190 191 /** assert that generic type 't' is well-formed */ 192 public void assertValidGenericType(Type t) { 193 assertValidGenericType(t, true); 194 } 195 196 /** assert that 's' is/is not assignable to 't' */ 197 public void assertValidGenericType(Type t, boolean expected) { 198 if (chk.checkValidGenericType(t) != expected) { 199 String msg = expected ? 200 " is not a valid generic type" : 201 " is a valid generic type"; 202 error(t + msg + " " + t.tsym.type); 203 } 204 } 205 // </editor-fold> 206 207 /** Creates an inference context given a list of type variables and performs the given action on it. 208 * The intention is to provide a way to do unit testing on inference contexts. 209 * @param typeVars a list of type variables to create the inference context from 210 * @param consumer the action to be performed on the inference context 211 */ 212 protected void withInferenceContext(List<Type> typeVars, Consumer<InferenceContext> consumer) { 213 Assert.check(!typeVars.isEmpty(), "invalid parameter, empty type variables list"); 214 ListBuffer undetVarsBuffer = new ListBuffer(); 215 typeVars.stream().map((tv) -> new UndetVar((TypeVar)tv, null, types)).forEach((undetVar) -> { 216 undetVarsBuffer.add(undetVar); 217 }); 218 List<Type> undetVarsList = undetVarsBuffer.toList(); 219 InferenceContext inferenceContext = new InferenceContext(infer, nil(), undetVarsList); 220 inferenceContext.rollback(undetVarsList); 221 consumer.accept(inferenceContext); 222 } 223 224 private void error(String msg) { 225 throw new AssertionError("Unexpected result: " + msg); 226 } 227 228 // <editor-fold defaultstate="collapsed" desc="type functions"> 229 230 /** compute the erasure of a type 't' */ 231 public Type erasure(Type t) { 232 return types.erasure(t); 233 } 234 235 /** compute the capture of a type 't' */ 236 public Type capture(Type t) { 237 return types.capture(t); 238 } 239 240 /** compute the boxed type associated with 't' */ 241 public Type box(Type t) { 242 if (!t.isPrimitive()) { 243 throw new AssertionError("Cannot box non-primitive type: " + t); 244 } 245 return types.boxedClass(t).type; 246 } 247 248 /** compute the unboxed type associated with 't' */ 249 public Type unbox(Type t) { 250 Type u = types.unboxedType(t); 251 if (t == null) { 252 throw new AssertionError("Cannot unbox reference type: " + t); 253 } else { 254 return u; 255 } 256 } 257 258 /** compute a type substitution on 't' given a list of type mappings */ 259 public Type subst(Type t, Mapping... maps) { 260 ListBuffer<Type> from = new ListBuffer<>(); 261 ListBuffer<Type> to = new ListBuffer<>(); 262 for (Mapping tm : maps) { 263 from.append(tm.from); 264 to.append(tm.to); 265 } 266 return types.subst(t, from.toList(), to.toList()); 267 } 268 269 /** create a fresh type mapping from a type to another */ 270 public Mapping Mapping(Type from, Type to) { 271 return new Mapping(from, to); 272 } 273 274 public static class Mapping { 275 Type from; 276 Type to; 277 private Mapping(Type from, Type to) { 278 this.from = from; 279 this.to = to; 280 } 281 } 282 // </editor-fold> 283 284 // <editor-fold defaultstate="collapsed" desc="type factory"> 285 286 /** 287 * This class is used to create Java types in a simple way. All main 288 * kinds of type are supported: primitive, reference, non-denotable. The 289 * factory also supports creation of constant types (used by the compiler 290 * to represent the type of a literal). 291 */ 292 public class Factory { 293 294 private int synthNameCount = 0; 295 296 private Name syntheticName() { 297 return names.fromString("A$" + synthNameCount++); 298 } 299 300 public ClassType Class(long flags, Type... typeArgs) { 301 ClassSymbol csym = new ClassSymbol(flags, syntheticName(), predef.noSymbol); 302 csym.type = new ClassType(Type.noType, List.from(typeArgs), csym); 303 ((ClassType)csym.type).supertype_field = predef.objectType; 304 return (ClassType)csym.type; 305 } 306 307 public ClassType Class(Type... typeArgs) { 308 return Class(0, typeArgs); 309 } 310 311 public ClassType Interface(Type... typeArgs) { 312 return Class(Flags.INTERFACE, typeArgs); 313 } 314 315 public ClassType Interface(long flags, Type... typeArgs) { 316 return Class(Flags.INTERFACE | flags, typeArgs); 317 } 318 319 public Type Constant(byte b) { 320 return predef.byteType.constType(b); 321 } 322 323 public Type Constant(short s) { 324 return predef.shortType.constType(s); 325 } 326 327 public Type Constant(int i) { 328 return predef.intType.constType(i); 329 } 330 331 public Type Constant(long l) { 332 return predef.longType.constType(l); 333 } 334 335 public Type Constant(float f) { 336 return predef.floatType.constType(f); 337 } 338 339 public Type Constant(double d) { 340 return predef.doubleType.constType(d); 341 } 342 343 public Type Constant(char c) { 344 return predef.charType.constType(c + 0); 345 } 346 347 public ArrayType Array(Type elemType) { 348 return new ArrayType(elemType, predef.arrayClass); 349 } 350 351 public TypeVar TypeVariable() { 352 return TypeVariable(predef.objectType); 353 } 354 355 public TypeVar TypeVariable(Type bound) { 356 TypeSymbol tvsym = new TypeVariableSymbol(0, syntheticName(), null, predef.noSymbol); 357 tvsym.type = new TypeVar(tvsym, bound, null); 358 return (TypeVar)tvsym.type; 359 } 360 361 public WildcardType Wildcard(BoundKind bk, Type bound) { 362 return new WildcardType(bound, bk, predef.boundClass); 363 } 364 365 public CapturedType CapturedVariable(Type upper, Type lower) { 366 return new CapturedType(syntheticName(), predef.noSymbol, upper, lower, null); 367 } 368 369 public ClassType Intersection(Type classBound, Type... intfBounds) { 370 ClassType ct = Class(Flags.COMPOUND); 371 ct.supertype_field = classBound; 372 ct.interfaces_field = List.from(intfBounds); 373 return ct; 374 } 375 } 376 // </editor-fold> 377 378 // <editor-fold defaultstate="collapsed" desc="StrToTypeFactory"> 379 /** 380 * StrToTypeFactory is a class provided to ease the creation of complex types from Strings. 381 * The client code can specify a package, a list of imports and a list of type variables when 382 * creating an instance of StrToTypeFactory. Later types including, or not, these type variables 383 * can be created by the factory. All occurrences of the same type variable in a type defined 384 * using a String are guaranteed to refer to the same type variable in the created type. 385 * 386 * An example is reported below: 387 * 388 * <pre> 389 * List<String> imports = new ArrayList<>(); 390 * imports.add("java.util.*"); 391 * List<String> typeVars = new ArrayList<>(); 392 * typeVars.add("T"); 393 * strToTypeFactory = new StrToTypeFactory(null, imports, typeVars); 394 * 395 * Type freeType = strToTypeFactory.getType("List<? extends T>"); 396 * Type aType = strToTypeFactory.getType("List<? extends String>"); 397 * 398 * // method withInferenceContext() belongs to TypeHarness 399 * withInferenceContext(strToTypeFactory.getTypeVars(), inferenceContext -> { 400 * assertSameType(inferenceContext.asUndetVar(freeType), aType); 401 * UndetVar undetVarForT = (UndetVar)inferenceContext.undetVars().head; 402 * com.sun.tools.javac.util.List<Type> equalBounds = undetVarForT.getBounds(InferenceBound.EQ); 403 * Assert.check(!equalBounds.isEmpty() && equalBounds.length() == 1, 404 * "undetVar must have only one equality bound"); 405 * }); 406 * </pre> 407 */ 408 public class StrToTypeFactory { 409 int id = 0; 410 String pkg; 411 java.util.List<String> imports; 412 public java.util.List<String> typeVarDecls; 413 public List<Type> typeVariables; 414 415 public StrToTypeFactory(String pkg, java.util.List<String> imports, java.util.List<String> typeVarDecls) { 416 this.pkg = pkg; 417 this.imports = imports; 418 this.typeVarDecls = typeVarDecls == null ? new ArrayList<>() : typeVarDecls; 419 this.typeVariables = from(this.typeVarDecls.stream() 420 .map(this::typeVarName) 421 .map(this::getType) 422 .collect(Collectors.toList()) 423 ); 424 } 425 426 TypeVar getTypeVarFromStr(String name) { 427 if (typeVarDecls.isEmpty()) { 428 return null; 429 } 430 int index = typeVarDecls.indexOf(name); 431 if (index != -1) { 432 return (TypeVar)typeVariables.get(index); 433 } 434 return null; 435 } 436 437 List<Type> getTypeVars() { 438 return typeVariables; 439 } 440 441 String typeVarName(String typeVarDecl) { 442 String[] ss = typeVarDecl.split(" "); 443 return ss[0]; 444 } 445 446 public final Type getType(String type) { 447 JavaSource source = new JavaSource(type); 448 MyAttr.theType = null; 449 MyAttr.typeParameters = List.nil(); 450 tool.clear(); 451 List<JavaFileObject> inputs = of(source); 452 try { 453 tool.compile(inputs); 454 } catch (Throwable ex) { 455 throw new Abort(ex); 456 } 457 if (typeVariables != null) { 458 return types.subst(MyAttr.theType, MyAttr.typeParameters, typeVariables); 459 } 460 return MyAttr.theType; 461 } 462 463 class JavaSource extends SimpleJavaFileObject { 464 465 String id; 466 String type; 467 String template = "#Package;\n" + 468 "#Imports\n" + 469 "class G#Id#TypeVars {\n" + 470 " #FieldType var;" + 471 "}"; 472 473 JavaSource(String type) { 474 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 475 this.id = String.valueOf(StrToTypeFactory.this.id++); 476 this.type = type; 477 } 478 479 @Override 480 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 481 String impStmts = imports.size() > 0 ? 482 imports.stream().map(i -> "import " + i + ";").collect(Collectors.joining("\n")) : ""; 483 String tvars = !typeVarDecls.isEmpty() ? 484 typeVarDecls.stream().collect(Collectors.joining(",", "<", ">")) : ""; 485 return template 486 .replace("#Package", (pkg == null) ? "" : "package " + pkg + ";") 487 .replace("#Imports", impStmts) 488 .replace("#Id", id) 489 .replace("#TypeVars", tvars) 490 .replace("#FieldType", type); 491 } 492 } 493 } 494 // </editor-fold> 495 496 // <editor-fold defaultstate="collapsed" desc="helper classes"> 497 static class MyAttr extends Attr { 498 499 private static Type theType; 500 private static List<Type> typeParameters = List.nil(); 501 502 static void preRegister(Context context) { 503 context.put(attrKey, (com.sun.tools.javac.util.Context.Factory<Attr>) c -> new MyAttr(c)); 504 } 505 506 MyAttr(Context context) { 507 super(context); 508 } 509 510 @Override 511 public void visitVarDef(JCVariableDecl tree) { 512 super.visitVarDef(tree); 513 theType = tree.type; 514 } 515 516 @Override 517 public void attribClass(DiagnosticPosition pos, ClassSymbol c) { 518 super.attribClass(pos, c); 519 ClassType ct = (ClassType)c.type; 520 typeParameters = ct.typarams_field; 521 } 522 } 523 524 static class ReusableJavaCompiler extends JavaCompiler { 525 ReusableJavaCompiler(Context context) { 526 super(context); 527 } 528 529 @Override 530 protected void checkReusable() { 531 // do nothing 532 } 533 534 @Override 535 public void close() { 536 //do nothing 537 } 538 539 void clear() { 540 newRound(); 541 Modules.instance(context).newRound(); 542 } 543 } 544 // </editor-fold> 545} 546