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