7007432: Test generic types well-formedness
authormcimadamore
Fri, 14 Jan 2011 09:45:52 +0000
changeset 8037 b3f278fe95d6
parent 8036 17b976649c61
child 8038 4a2973290d71
7007432: Test generic types well-formedness Summary: add a new kind of check (well-formedness of generic type w.r.t. declared bounds) in the type-harness Reviewed-by: jjg
langtools/src/share/classes/com/sun/tools/javac/comp/Check.java
langtools/test/tools/javac/types/GenericTypeWellFormednessTest.java
langtools/test/tools/javac/types/TypeHarness.java
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Check.java	Fri Jan 14 09:45:04 2011 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Check.java	Fri Jan 14 09:45:52 2011 +0000
@@ -506,43 +506,18 @@
      *  @param a             The type that should be bounded by bs.
      *  @param bs            The bound.
      */
-    private void checkExtends(DiagnosticPosition pos, Type a, TypeVar bs) {
+    private boolean checkExtends(Type a, TypeVar bs) {
          if (a.isUnbound()) {
-             return;
+             return true;
          } else if (a.tag != WILDCARD) {
              a = types.upperBound(a);
-             for (List<Type> l = types.getBounds(bs); l.nonEmpty(); l = l.tail) {
-                 if (!types.isSubtype(a, l.head)) {
-                     log.error(pos, "not.within.bounds", a);
-                     return;
-                 }
-             }
+             return types.isSubtype(a, bs.bound);
          } else if (a.isExtendsBound()) {
-             if (!types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings))
-                 log.error(pos, "not.within.bounds", a);
+             return types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings);
          } else if (a.isSuperBound()) {
-             if (types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound()))
-                 log.error(pos, "not.within.bounds", a);
+             return !types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound());
          }
-     }
-
-    /** Check that a type is within some bounds.
-     *
-     *  Used in TypeApply to verify that, e.g., X in V<X> is a valid
-     *  type argument.
-     *  @param pos           Position to be used for error reporting.
-     *  @param a             The type that should be bounded by bs.
-     *  @param bs            The bound.
-     */
-    private void checkCapture(JCTypeApply tree) {
-        List<JCExpression> args = tree.getTypeArguments();
-        for (Type arg : types.capture(tree.type).getTypeArguments()) {
-            if (arg.tag == TYPEVAR && arg.getUpperBound().isErroneous()) {
-                log.error(args.head.pos, "not.within.bounds", args.head.type);
-                break;
-            }
-            args = args.tail;
-        }
+         return true;
      }
 
     /** Check that type is different from 'void'.
@@ -775,6 +750,74 @@
         }
     }
 
+    /**
+     * Check that type 't' is a valid instantiation of a generic class
+     * (see JLS 4.5)
+     *
+     * @param t class type to be checked
+     * @return true if 't' is well-formed
+     */
+    public boolean checkValidGenericType(Type t) {
+        return firstIncompatibleTypeArg(t) == null;
+    }
+    //WHERE
+        private Type firstIncompatibleTypeArg(Type type) {
+            List<Type> formals = type.tsym.type.allparams();
+            List<Type> actuals = type.allparams();
+            List<Type> args = type.getTypeArguments();
+            List<Type> forms = type.tsym.type.getTypeArguments();
+            ListBuffer<Type> tvars_buf = new ListBuffer<Type>();
+
+            // For matching pairs of actual argument types `a' and
+            // formal type parameters with declared bound `b' ...
+            while (args.nonEmpty() && forms.nonEmpty()) {
+                // exact type arguments needs to know their
+                // bounds (for upper and lower bound
+                // calculations).  So we create new TypeVars with
+                // bounds substed with actuals.
+                tvars_buf.append(types.substBound(((TypeVar)forms.head),
+                                                  formals,
+                                                  actuals));
+                args = args.tail;
+                forms = forms.tail;
+            }
+
+            args = type.getTypeArguments();
+            List<Type> tvars_cap = types.substBounds(formals,
+                                      formals,
+                                      types.capture(type).allparams());
+            while (args.nonEmpty() && tvars_cap.nonEmpty()) {
+                // Let the actual arguments know their bound
+                args.head.withTypeVar((TypeVar)tvars_cap.head);
+                args = args.tail;
+                tvars_cap = tvars_cap.tail;
+            }
+
+            args = type.getTypeArguments();
+            List<Type> tvars = tvars_buf.toList();
+
+            while (args.nonEmpty() && tvars.nonEmpty()) {
+                Type actual = types.subst(args.head,
+                    type.tsym.type.getTypeArguments(),
+                    tvars_buf.toList());
+                if (!checkExtends(actual, (TypeVar)tvars.head)) {
+                    return args.head;
+                }
+                args = args.tail;
+                tvars = tvars.tail;
+            }
+
+            args = type.getTypeArguments();
+
+            for (Type arg : types.capture(type).getTypeArguments()) {
+                if (arg.tag == TYPEVAR && arg.getUpperBound().isErroneous()) {
+                    return args.head;
+                }
+            }
+
+            return null;
+        }
+
     /** Check that given modifiers are legal for given symbol and
      *  return modifiers together with any implicit modififiers for that symbol.
      *  Warning: we can't use flags() here since this method
@@ -987,11 +1030,17 @@
         @Override
         public void visitTypeApply(JCTypeApply tree) {
             if (tree.type.tag == CLASS) {
-                List<Type> formals = tree.type.tsym.type.allparams();
-                List<Type> actuals = tree.type.allparams();
                 List<JCExpression> args = tree.arguments;
                 List<Type> forms = tree.type.tsym.type.getTypeArguments();
-                ListBuffer<Type> tvars_buf = new ListBuffer<Type>();
+
+                Type incompatibleArg = firstIncompatibleTypeArg(tree.type);
+                if (incompatibleArg != null) {
+                    for (JCTree arg : tree.arguments) {
+                        if (arg.type == incompatibleArg) {
+                            log.error(arg, "not.within.bounds", incompatibleArg);
+                        }
+                    }
+                }
 
                 boolean is_java_lang_Class = tree.type.tsym.flatName() == names.java_lang_Class;
 
@@ -1001,46 +1050,10 @@
                     validateTree(args.head,
                             !(isOuter && is_java_lang_Class),
                             false);
-
-                    // exact type arguments needs to know their
-                    // bounds (for upper and lower bound
-                    // calculations).  So we create new TypeVars with
-                    // bounds substed with actuals.
-                    tvars_buf.append(types.substBound(((TypeVar)forms.head),
-                                                      formals,
-                                                      actuals));
-
                     args = args.tail;
                     forms = forms.tail;
                 }
 
-                args = tree.arguments;
-                List<Type> tvars_cap = types.substBounds(formals,
-                                          formals,
-                                          types.capture(tree.type).allparams());
-                while (args.nonEmpty() && tvars_cap.nonEmpty()) {
-                    // Let the actual arguments know their bound
-                    args.head.type.withTypeVar((TypeVar)tvars_cap.head);
-                    args = args.tail;
-                    tvars_cap = tvars_cap.tail;
-                }
-
-                args = tree.arguments;
-                List<Type> tvars = tvars_buf.toList();
-
-                while (args.nonEmpty() && tvars.nonEmpty()) {
-                    Type actual = types.subst(args.head.type,
-                        tree.type.tsym.type.getTypeArguments(),
-                        tvars_buf.toList());
-                    checkExtends(args.head.pos(),
-                                 actual,
-                                 (TypeVar)tvars.head);
-                    args = args.tail;
-                    tvars = tvars.tail;
-                }
-
-                checkCapture(tree);
-
                 // Check that this type is either fully parameterized, or
                 // not parameterized at all.
                 if (tree.type.getEnclosingType().isRaw())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/types/GenericTypeWellFormednessTest.java	Fri Jan 14 09:45:52 2011 +0000
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2010, 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 7007432 7006109
+ * @summary Test generic types well-formedness
+ * @author mcimadamore
+ * @library .
+ * @run main GenericTypeWellFormednessTest
+ */
+
+import com.sun.tools.javac.code.BoundKind;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.*;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.*;
+import java.lang.reflect.Array;
+
+/**
+ * Check parameterized type well-formedness. This test executes a number of checks
+ * in order to establish as to whether an instantiation of a generic type conforms
+ * to the generic class' declared bounds.
+ */
+public class GenericTypeWellFormednessTest extends TypeHarness {
+
+    static int executedCount = 0;
+    static int ignoredCount = 0;
+
+    InstantiableType[] rows;
+    Type[] columns;
+
+    static class InstantiableType {
+        protected Type type;
+
+        public InstantiableType(Type type) {
+            this.type = type;
+        }
+
+        Type inst(Type clazz) {
+            return type;
+        }
+    }
+
+    enum Result {
+        /* generic type is well-formed w.r.t. declared bounds */
+        OK(true),
+        /* generic type is not well-formed w.r.t. declared bounds */
+        FAIL(false),
+        /* generic type is not well-formed w.r.t. declared bounds according to the JLS 3rd,
+         * but javac allows it (spec for generic type well-formedness is overly restrictive)
+         * See regression test test/tools/generics/wildcards/T5097548.java
+         */
+        IGNORE(false);
+
+        boolean value;
+
+        Result(boolean value) {
+            this.value = value;
+        }
+    }
+
+    static final Result T = Result.OK;
+    static final Result F = Result.FAIL;
+    static final Result I = Result.IGNORE;
+
+    /*is a type in 'rows' a valid instantiation for the generic class in 'col' ? */
+    Result[][] isValidInstantiation = {
+                     //Foo<X>, Foo<X ext Object>, Foo<X ext Number>, Foo<X ext Foo<X>>, Foo<X ext Foo<+X>>, Foo<X ext Foo<-X>>, Foo<X ext Foo<?>>
+    /*Foo<Object>*/  { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<Number>*/  { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<Integer>*/ { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<Double>*/  { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<String>*/  { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<X1>*/      { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<X2>*/      { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<X3>*/      { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<X4>*/      { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<X5>*/      { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<X6>*/      { T     , T                , F                , T                , T                 , T                 , T },
+    /*Foo<+Object>*/ { T     , T                , I                , I                , I                 , I                 , I },
+    /*Foo<+Number>*/ { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<+Integer>*/{ T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<+Double>*/ { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<+String>*/ { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<+X1>*/     { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<+X2>*/     { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<+X3>*/     { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<+X4>*/     { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<+X5>*/     { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<+X6>*/     { T     , T                , F                , T                , T                 , I                 , T },
+    /*Foo<-Object>*/ { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<-Number>*/ { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<-Integer>*/{ T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<-Double>*/ { T     , T                , T                , F                , F                 , F                 , F },
+    /*Foo<-String>*/ { T     , T                , F                , F                , F                 , F                 , F },
+    /*Foo<-X1>*/     { T     , T                , I                , I                , I                 , I                 , I },
+    /*Foo<-X2>*/     { T     , T                , I                , F                , F                 , F                 , F },
+    /*Foo<-X3>*/     { T     , T                , I                , F                , F                 , F                 , F },
+    /*Foo<-X4>*/     { T     , T                , I                , F                , F                 , F                 , F },
+    /*Foo<-X5>*/     { T     , T                , I                , F                , F                 , F                 , F },
+    /*Foo<-X6>*/     { T     , T                , F                , T                , I                 , I                 , T },
+    /*Foo<?>*/       { T     , T                , T                , T                , T                 , T                 , T }};
+
+    GenericTypeWellFormednessTest() {
+        InstantiableType[] basicTypes = {
+            new InstantiableType(predef.objectType),
+            new InstantiableType(NumberType()),
+            new InstantiableType(box(predef.intType)),
+            new InstantiableType(box(predef.doubleType)),
+            new InstantiableType(predef.stringType) };
+
+        InstantiableType[] typeVars = new InstantiableType[basicTypes.length + 1];
+        for (int i = 0 ; i < basicTypes.length ; i++) {
+           typeVars[i] = new InstantiableType(fac.TypeVariable(basicTypes[i].type));
+        }
+        typeVars[typeVars.length - 1] = new InstantiableType(null) {
+            Type inst(Type clazz) {
+                TypeVar tvar = fac.TypeVariable();
+                tvar.bound = subst(clazz, Mapping(clazz.getTypeArguments().head, tvar));
+                return tvar;
+            }
+        };
+
+        InstantiableType[] typeArgs = join(InstantiableType.class, basicTypes, typeVars);
+
+        InstantiableType[] invariantTypes = new InstantiableType[typeArgs.length];
+        for (int i = 0 ; i < typeArgs.length ; i++) {
+           final InstantiableType type1 = typeArgs[i];
+           invariantTypes[i] = new InstantiableType(typeArgs[i].type) {
+               Type inst(Type clazz) {
+                   return subst(clazz, Mapping(clazz.getTypeArguments().head, type1.inst(clazz)));
+               }
+            };
+        }
+
+        InstantiableType[] covariantTypes = new InstantiableType[typeArgs.length];
+        for (int i = 0 ; i < typeArgs.length ; i++) {
+           final InstantiableType type1 = typeArgs[i];
+           covariantTypes[i] = new InstantiableType(null) {
+               Type inst(Type clazz) {
+                   Type t = fac.Wildcard(BoundKind.EXTENDS, type1.inst(clazz));
+                   return subst(clazz, Mapping(clazz.getTypeArguments().head, t));
+               }
+            };
+        }
+
+        InstantiableType[] contravariantTypes = new InstantiableType[typeArgs.length];
+        for (int i = 0 ; i < typeArgs.length ; i++) {
+           final InstantiableType type1 = typeArgs[i];
+           contravariantTypes[i] = new InstantiableType(null) {
+               Type inst(Type clazz) {
+                   Type t = fac.Wildcard(BoundKind.SUPER, type1.inst(clazz));
+                   return subst(clazz, Mapping(clazz.getTypeArguments().head, t));
+               }
+            };
+        }
+
+        InstantiableType[] bivariantTypes = {
+            new InstantiableType(fac.Wildcard(BoundKind.UNBOUND, predef.objectType)) {
+               Type inst(Type clazz) {
+                   return subst(clazz, Mapping(clazz.getTypeArguments().head, type));
+               }
+            }
+        };
+
+        rows = join(InstantiableType.class, invariantTypes, covariantTypes, contravariantTypes, bivariantTypes);
+
+        Type tv1 = fac.TypeVariable();
+        Type decl1 = fac.Class(tv1);
+
+        Type tv2 = fac.TypeVariable(predef.objectType);
+        Type decl2 = fac.Class(tv2);
+
+        Type tv3 = fac.TypeVariable(NumberType());
+        Type decl3 = fac.Class(tv3);
+
+        TypeVar tv4 = fac.TypeVariable();
+        Type decl4 = fac.Class(tv4);
+        tv4.bound = decl4;
+        tv4.tsym.name = predef.exceptionType.tsym.name;
+
+        TypeVar tv5 = fac.TypeVariable();
+        Type decl5 = fac.Class(tv5);
+        tv5.bound = subst(decl5, Mapping(tv5, fac.Wildcard(BoundKind.EXTENDS, tv5)));
+
+        TypeVar tv6 = fac.TypeVariable();
+        Type decl6 = fac.Class(tv6);
+        tv6.bound = subst(decl6, Mapping(tv6, fac.Wildcard(BoundKind.SUPER, tv6)));
+
+        TypeVar tv7 = fac.TypeVariable();
+        Type decl7 = fac.Class(tv7);
+        tv7.bound = subst(decl7, Mapping(tv7, fac.Wildcard(BoundKind.UNBOUND, predef.objectType)));
+
+        columns = new Type[] {
+            decl1, decl2, decl3, decl4, decl5, decl6, decl7
+        };
+    }
+
+    void test() {
+        for (int i = 0; i < rows.length ; i++) {
+            for (int j = 0; j < columns.length ; j++) {
+                Type decl = columns[j];
+                Type inst = rows[i].inst(decl);
+                if (isValidInstantiation[i][j] != Result.IGNORE) {
+                    executedCount++;
+                    assertValidGenericType(inst, isValidInstantiation[i][j].value);
+                } else {
+                    ignoredCount++;
+                }
+            }
+        }
+    }
+
+    Type NumberType() {
+        Symbol s = box(predef.intType).tsym;
+        s.complete();
+        return ((ClassType)s.type).supertype_field;
+    }
+
+    @SuppressWarnings("unchecked")
+    <T> T[] join(Class<T> type, T[]... args) {
+        int totalLength = 0;
+        for (T[] arr : args) {
+            totalLength += arr.length;
+        }
+        T[] new_arr = (T[])Array.newInstance(type, totalLength);
+        int idx = 0;
+        for (T[] arr : args) {
+            System.arraycopy(arr, 0, new_arr, idx, arr.length);
+            idx += arr.length;
+        }
+        return new_arr;
+    }
+
+    public static void main(String[] args) {
+        new GenericTypeWellFormednessTest().test();
+        System.out.println("Executed checks : " + executedCount);
+        System.out.println("Ignored checks : " + ignoredCount);
+    }
+}
--- a/langtools/test/tools/javac/types/TypeHarness.java	Fri Jan 14 09:45:04 2011 +0000
+++ b/langtools/test/tools/javac/types/TypeHarness.java	Fri Jan 14 09:45:52 2011 +0000
@@ -29,6 +29,7 @@
 import com.sun.tools.javac.code.Type;
 import com.sun.tools.javac.code.Type.*;
 import com.sun.tools.javac.code.Symbol.*;
+import com.sun.tools.javac.comp.Check;
 import com.sun.tools.javac.util.List;
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Name;
@@ -68,6 +69,7 @@
 public class TypeHarness {
 
     protected Types types;
+    protected Check chk;
     protected Symtab predef;
     protected Names names;
     protected Factory fac;
@@ -76,6 +78,7 @@
         Context ctx = new Context();
         JavacFileManager.preRegister(ctx);
         types = Types.instance(ctx);
+        chk = Check.instance(ctx);
         predef = Symtab.instance(ctx);
         names = Names.instance(ctx);
         fac = new Factory();
@@ -157,6 +160,21 @@
             error(s + msg + t);
         }
     }
+
+    /** assert that generic type 't' is well-formed */
+    public void assertValidGenericType(Type t) {
+        assertValidGenericType(t, true);
+    }
+
+    /** assert that 's' is/is not assignable to 't' */
+    public void assertValidGenericType(Type t, boolean expected) {
+        if (chk.checkValidGenericType(t) != expected) {
+            String msg = expected ?
+                " is not a valid generic type" :
+                " is a valid generic type";
+            error(t + msg + "   " + t.tsym.type);
+        }
+    }
     // </editor-fold>
 
     private void error(String msg) {