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