8042239: javac: TreeMaker.Type(Type t) does not handle UnionClassType
Summary: Enhancing TreeMaker.Type(Type t) with support for UnionClassType and IntersectionClassType.
Reviewed-by: vromero, rfield
Contributed-by: robert.field@oracle.com, jan.lahoda@oracle.com
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Type.java Mon Jul 07 13:39:31 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Type.java Tue Jul 08 15:13:16 2014 +0200
@@ -1050,6 +1050,10 @@
public <R, P> R accept(TypeVisitor<R, P> v, P p) {
return v.visitUnion(this, p);
}
+
+ public Iterable<? extends Type> getAlternativeTypes() {
+ return alternatives_field;
+ }
}
// a clone of a ClassType that knows about the bounds of an intersection type.
--- a/langtools/src/share/classes/com/sun/tools/javac/tree/TreeMaker.java Mon Jul 07 13:39:31 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/tree/TreeMaker.java Tue Jul 08 15:13:16 2014 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -692,13 +692,36 @@
break;
}
case CLASS:
- Type outer = t.getEnclosingType();
- JCExpression clazz = outer.hasTag(CLASS) && t.tsym.owner.kind == TYP
- ? Select(Type(outer), t.tsym)
- : QualIdent(t.tsym);
- tp = t.getTypeArguments().isEmpty()
- ? clazz
- : TypeApply(clazz, Types(t.getTypeArguments()));
+ switch (t.getKind()) {
+ case UNION: {
+ UnionClassType tu = (UnionClassType)t;
+ ListBuffer<JCExpression> la = new ListBuffer<>();
+ for (Type ta : tu.getAlternativeTypes()) {
+ la.add(Type(ta));
+ }
+ tp = TypeUnion(la.toList());
+ break;
+ }
+ case INTERSECTION: {
+ IntersectionClassType it = (IntersectionClassType)t;
+ ListBuffer<JCExpression> la = new ListBuffer<>();
+ for (Type ta : it.getExplicitComponents()) {
+ la.add(Type(ta));
+ }
+ tp = TypeIntersection(la.toList());
+ break;
+ }
+ default: {
+ Type outer = t.getEnclosingType();
+ JCExpression clazz = outer.hasTag(CLASS) && t.tsym.owner.kind == TYP
+ ? Select(Type(outer), t.tsym)
+ : QualIdent(t.tsym);
+ tp = t.getTypeArguments().isEmpty()
+ ? clazz
+ : TypeApply(clazz, Types(t.getTypeArguments()));
+ break;
+ }
+ }
break;
case ARRAY:
tp = TypeArray(Type(types.elemtype(t)));
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/tree/MakeTypeTest.java Tue Jul 08 15:13:16 2014 +0200
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8042239
+ * @summary Verify that TreeMaker.Type(Type) can handle all reasonable types
+ * @library /tools/javac/lib
+ * @build JavacTestingAbstractProcessor MakeTypeTest
+ * @compile/process/ref=MakeTypeTest.out -processor MakeTypeTest MakeTypeTest.java
+ */
+
+import java.lang.annotation.*;
+import java.util.*;
+
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.*;
+import javax.lang.model.type.*;
+
+import com.sun.source.tree.*;
+import com.sun.source.util.*;
+import com.sun.tools.javac.api.JavacTrees;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.*;
+import com.sun.tools.javac.util.List;
+
+public class MakeTypeTest extends JavacTestingAbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (!roundEnv.processingOver())
+ return false;
+
+ JavacTask.instance(processingEnv).addTaskListener(new TaskListener() {
+ @Override
+ public void started(TaskEvent e) {
+ }
+ @Override
+ public void finished(TaskEvent e) {
+ if (e.getKind() == TaskEvent.Kind.ANALYZE &&
+ e.getTypeElement().getQualifiedName().contentEquals("MakeTypeTest")) {
+ doTest();
+ }
+ }
+ });
+
+ return false;
+ }
+
+ void doTest() {
+ //go through this file, look for @TestType and verify TreeMaker.Type behavior:
+ Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext();
+ JavacTrees trees = JavacTrees.instance(ctx);
+ TypeElement testType = processingEnv.getElementUtils().getTypeElement("MakeTypeTest");
+ TreePath path = trees.getPath(testType);
+
+ Set<TypeKind> unseenTypeKinds = EnumSet.allOf(TypeKind.class);
+
+ new TreePathScanner<Void, Void>() {
+ @Override
+ public Void visitVariable(VariableTree node, Void p) {
+ handleDecl(new TreePath(getCurrentPath(), node.getType()));
+ return super.visitVariable(node, p);
+ }
+
+ @Override
+ public Void visitMethod(MethodTree node, Void p) {
+ if (node.getReturnType() != null)
+ handleDecl(new TreePath(getCurrentPath(), node.getReturnType()));
+ return super.visitMethod(node, p);
+ }
+
+ @Override
+ public Void visitTypeParameter(TypeParameterTree node, Void p) {
+ TypeVariable type = (TypeVariable) trees.getTypeMirror(getCurrentPath());
+ TreePath aBoundPath = new TreePath(getCurrentPath(), node.getBounds().get(0));
+ handleDecl(aBoundPath, (Type) type.getUpperBound());
+ return super.visitTypeParameter(node, p);
+ }
+
+ void handleDecl(TreePath typePath) {
+ handleDecl(typePath, (Type) trees.getTypeMirror(typePath));
+ }
+
+ void handleDecl(TreePath typePath, Type type) {
+ Element decl = trees.getElement(typePath.getParentPath());
+ TestType testType = decl.getAnnotation(TestType.class);
+
+ if (testType == null) return ;
+
+ if (testType.nested() >= 0) {
+ ClassType ct = (ClassType) type;
+ type = ct.getTypeArguments().get(testType.nested());
+ }
+
+ JCExpression typeExpr = TreeMaker.instance(ctx).Type(type);
+
+ if (!typeExpr.getKind().equals(testType.expectedKind())) {
+ throw new IllegalStateException("was=" + typeExpr + ", kind=" +
+ typeExpr.getKind() + "; expected kind: " +
+ testType.expectedKind() + "; type=" + type);
+ }
+ unseenTypeKinds.remove(type.getKind());
+ }
+
+ }.scan(path, null);
+
+ unseenTypeKinds.removeAll(Arrays.asList(TypeKind.NONE, TypeKind.NULL, TypeKind.ERROR,
+ TypeKind.PACKAGE, TypeKind.EXECUTABLE, TypeKind.OTHER));
+
+ if (!unseenTypeKinds.isEmpty())
+ throw new IllegalStateException("Unhandled types=" + unseenTypeKinds);
+
+ System.err.println("done.");
+ }
+
+ //the following defines the Types that should be passed into TreeMaker.Type and
+ //the expected resulting Tree kind:
+
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ boolean f1;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ byte f2;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ char f3;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ double f4;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ float f5;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ int f6;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ long f7;
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ short f8;
+ @TestType(expectedKind=Tree.Kind.PARAMETERIZED_TYPE)
+ List<? extends String> f9;
+ @TestType(expectedKind=Tree.Kind.ARRAY_TYPE)
+ int[] fa;
+ @TestType(expectedKind=Tree.Kind.EXTENDS_WILDCARD, nested = 0)
+ List<? extends String> fb;
+ @TestType(expectedKind=Tree.Kind.SUPER_WILDCARD, nested = 0)
+ List<? super String> fc;
+ @TestType(expectedKind=Tree.Kind.UNBOUNDED_WILDCARD, nested = 0)
+ List<?> fd;
+
+ @TestType(expectedKind=Tree.Kind.PRIMITIVE_TYPE)
+ void voidMethod() {
+ try {
+ voidMethod();
+ } catch (@TestType(expectedKind=Tree.Kind.UNION_TYPE) NullPointerException |
+ IllegalStateException ex) {
+ }
+ }
+
+ class WithTypeParam<@TestType(expectedKind=Tree.Kind.INTERSECTION_TYPE)
+ T extends CharSequence & Runnable> {
+ @TestType(expectedKind=Tree.Kind.IDENTIFIER)
+ T voidMethod() {
+ return null;
+ }
+ }
+
+}
+
+//TreeMaker.Type will be tested for the type the element annotated by this annotation
+@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD,
+ ElementType.PARAMETER, ElementType.TYPE_PARAMETER})
+@interface TestType {
+ //the expected Tree kind of the Tree that will be returned from TreeMaker.Type for the type
+ public Tree.Kind expectedKind();
+ //if >=0, the current type will be interpreted as a ClassType and the type to test will be
+ //the given type argument:
+ public int nested() default -1;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/tree/MakeTypeTest.out Tue Jul 08 15:13:16 2014 +0200
@@ -0,0 +1,1 @@
+done.