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