Fri, 14 Jan 2011 09:45:52 +0000
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
1.1 --- a/src/share/classes/com/sun/tools/javac/comp/Check.java Fri Jan 14 09:45:04 2011 +0000 1.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Check.java Fri Jan 14 09:45:52 2011 +0000 1.3 @@ -506,43 +506,18 @@ 1.4 * @param a The type that should be bounded by bs. 1.5 * @param bs The bound. 1.6 */ 1.7 - private void checkExtends(DiagnosticPosition pos, Type a, TypeVar bs) { 1.8 + private boolean checkExtends(Type a, TypeVar bs) { 1.9 if (a.isUnbound()) { 1.10 - return; 1.11 + return true; 1.12 } else if (a.tag != WILDCARD) { 1.13 a = types.upperBound(a); 1.14 - for (List<Type> l = types.getBounds(bs); l.nonEmpty(); l = l.tail) { 1.15 - if (!types.isSubtype(a, l.head)) { 1.16 - log.error(pos, "not.within.bounds", a); 1.17 - return; 1.18 - } 1.19 - } 1.20 + return types.isSubtype(a, bs.bound); 1.21 } else if (a.isExtendsBound()) { 1.22 - if (!types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings)) 1.23 - log.error(pos, "not.within.bounds", a); 1.24 + return types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings); 1.25 } else if (a.isSuperBound()) { 1.26 - if (types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound())) 1.27 - log.error(pos, "not.within.bounds", a); 1.28 + return !types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound()); 1.29 } 1.30 - } 1.31 - 1.32 - /** Check that a type is within some bounds. 1.33 - * 1.34 - * Used in TypeApply to verify that, e.g., X in V<X> is a valid 1.35 - * type argument. 1.36 - * @param pos Position to be used for error reporting. 1.37 - * @param a The type that should be bounded by bs. 1.38 - * @param bs The bound. 1.39 - */ 1.40 - private void checkCapture(JCTypeApply tree) { 1.41 - List<JCExpression> args = tree.getTypeArguments(); 1.42 - for (Type arg : types.capture(tree.type).getTypeArguments()) { 1.43 - if (arg.tag == TYPEVAR && arg.getUpperBound().isErroneous()) { 1.44 - log.error(args.head.pos, "not.within.bounds", args.head.type); 1.45 - break; 1.46 - } 1.47 - args = args.tail; 1.48 - } 1.49 + return true; 1.50 } 1.51 1.52 /** Check that type is different from 'void'. 1.53 @@ -775,6 +750,74 @@ 1.54 } 1.55 } 1.56 1.57 + /** 1.58 + * Check that type 't' is a valid instantiation of a generic class 1.59 + * (see JLS 4.5) 1.60 + * 1.61 + * @param t class type to be checked 1.62 + * @return true if 't' is well-formed 1.63 + */ 1.64 + public boolean checkValidGenericType(Type t) { 1.65 + return firstIncompatibleTypeArg(t) == null; 1.66 + } 1.67 + //WHERE 1.68 + private Type firstIncompatibleTypeArg(Type type) { 1.69 + List<Type> formals = type.tsym.type.allparams(); 1.70 + List<Type> actuals = type.allparams(); 1.71 + List<Type> args = type.getTypeArguments(); 1.72 + List<Type> forms = type.tsym.type.getTypeArguments(); 1.73 + ListBuffer<Type> tvars_buf = new ListBuffer<Type>(); 1.74 + 1.75 + // For matching pairs of actual argument types `a' and 1.76 + // formal type parameters with declared bound `b' ... 1.77 + while (args.nonEmpty() && forms.nonEmpty()) { 1.78 + // exact type arguments needs to know their 1.79 + // bounds (for upper and lower bound 1.80 + // calculations). So we create new TypeVars with 1.81 + // bounds substed with actuals. 1.82 + tvars_buf.append(types.substBound(((TypeVar)forms.head), 1.83 + formals, 1.84 + actuals)); 1.85 + args = args.tail; 1.86 + forms = forms.tail; 1.87 + } 1.88 + 1.89 + args = type.getTypeArguments(); 1.90 + List<Type> tvars_cap = types.substBounds(formals, 1.91 + formals, 1.92 + types.capture(type).allparams()); 1.93 + while (args.nonEmpty() && tvars_cap.nonEmpty()) { 1.94 + // Let the actual arguments know their bound 1.95 + args.head.withTypeVar((TypeVar)tvars_cap.head); 1.96 + args = args.tail; 1.97 + tvars_cap = tvars_cap.tail; 1.98 + } 1.99 + 1.100 + args = type.getTypeArguments(); 1.101 + List<Type> tvars = tvars_buf.toList(); 1.102 + 1.103 + while (args.nonEmpty() && tvars.nonEmpty()) { 1.104 + Type actual = types.subst(args.head, 1.105 + type.tsym.type.getTypeArguments(), 1.106 + tvars_buf.toList()); 1.107 + if (!checkExtends(actual, (TypeVar)tvars.head)) { 1.108 + return args.head; 1.109 + } 1.110 + args = args.tail; 1.111 + tvars = tvars.tail; 1.112 + } 1.113 + 1.114 + args = type.getTypeArguments(); 1.115 + 1.116 + for (Type arg : types.capture(type).getTypeArguments()) { 1.117 + if (arg.tag == TYPEVAR && arg.getUpperBound().isErroneous()) { 1.118 + return args.head; 1.119 + } 1.120 + } 1.121 + 1.122 + return null; 1.123 + } 1.124 + 1.125 /** Check that given modifiers are legal for given symbol and 1.126 * return modifiers together with any implicit modififiers for that symbol. 1.127 * Warning: we can't use flags() here since this method 1.128 @@ -987,11 +1030,17 @@ 1.129 @Override 1.130 public void visitTypeApply(JCTypeApply tree) { 1.131 if (tree.type.tag == CLASS) { 1.132 - List<Type> formals = tree.type.tsym.type.allparams(); 1.133 - List<Type> actuals = tree.type.allparams(); 1.134 List<JCExpression> args = tree.arguments; 1.135 List<Type> forms = tree.type.tsym.type.getTypeArguments(); 1.136 - ListBuffer<Type> tvars_buf = new ListBuffer<Type>(); 1.137 + 1.138 + Type incompatibleArg = firstIncompatibleTypeArg(tree.type); 1.139 + if (incompatibleArg != null) { 1.140 + for (JCTree arg : tree.arguments) { 1.141 + if (arg.type == incompatibleArg) { 1.142 + log.error(arg, "not.within.bounds", incompatibleArg); 1.143 + } 1.144 + } 1.145 + } 1.146 1.147 boolean is_java_lang_Class = tree.type.tsym.flatName() == names.java_lang_Class; 1.148 1.149 @@ -1001,46 +1050,10 @@ 1.150 validateTree(args.head, 1.151 !(isOuter && is_java_lang_Class), 1.152 false); 1.153 - 1.154 - // exact type arguments needs to know their 1.155 - // bounds (for upper and lower bound 1.156 - // calculations). So we create new TypeVars with 1.157 - // bounds substed with actuals. 1.158 - tvars_buf.append(types.substBound(((TypeVar)forms.head), 1.159 - formals, 1.160 - actuals)); 1.161 - 1.162 args = args.tail; 1.163 forms = forms.tail; 1.164 } 1.165 1.166 - args = tree.arguments; 1.167 - List<Type> tvars_cap = types.substBounds(formals, 1.168 - formals, 1.169 - types.capture(tree.type).allparams()); 1.170 - while (args.nonEmpty() && tvars_cap.nonEmpty()) { 1.171 - // Let the actual arguments know their bound 1.172 - args.head.type.withTypeVar((TypeVar)tvars_cap.head); 1.173 - args = args.tail; 1.174 - tvars_cap = tvars_cap.tail; 1.175 - } 1.176 - 1.177 - args = tree.arguments; 1.178 - List<Type> tvars = tvars_buf.toList(); 1.179 - 1.180 - while (args.nonEmpty() && tvars.nonEmpty()) { 1.181 - Type actual = types.subst(args.head.type, 1.182 - tree.type.tsym.type.getTypeArguments(), 1.183 - tvars_buf.toList()); 1.184 - checkExtends(args.head.pos(), 1.185 - actual, 1.186 - (TypeVar)tvars.head); 1.187 - args = args.tail; 1.188 - tvars = tvars.tail; 1.189 - } 1.190 - 1.191 - checkCapture(tree); 1.192 - 1.193 // Check that this type is either fully parameterized, or 1.194 // not parameterized at all. 1.195 if (tree.type.getEnclosingType().isRaw())
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/test/tools/javac/types/GenericTypeWellFormednessTest.java Fri Jan 14 09:45:52 2011 +0000 2.3 @@ -0,0 +1,261 @@ 2.4 +/* 2.5 + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 2.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2.7 + * 2.8 + * This code is free software; you can redistribute it and/or modify it 2.9 + * under the terms of the GNU General Public License version 2 only, as 2.10 + * published by the Free Software Foundation. 2.11 + * 2.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 2.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 2.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 2.15 + * version 2 for more details (a copy is included in the LICENSE file that 2.16 + * accompanied this code). 2.17 + * 2.18 + * You should have received a copy of the GNU General Public License version 2.19 + * 2 along with this work; if not, write to the Free Software Foundation, 2.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2.21 + * 2.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2.23 + * or visit www.oracle.com if you need additional information or have any 2.24 + * questions. 2.25 + */ 2.26 + 2.27 +/* 2.28 + * @test 2.29 + * @bug 7007432 7006109 2.30 + * @summary Test generic types well-formedness 2.31 + * @author mcimadamore 2.32 + * @library . 2.33 + * @run main GenericTypeWellFormednessTest 2.34 + */ 2.35 + 2.36 +import com.sun.tools.javac.code.BoundKind; 2.37 +import com.sun.tools.javac.code.Type; 2.38 +import com.sun.tools.javac.code.Type.*; 2.39 +import com.sun.tools.javac.code.Symbol; 2.40 +import com.sun.tools.javac.code.Symbol.*; 2.41 +import java.lang.reflect.Array; 2.42 + 2.43 +/** 2.44 + * Check parameterized type well-formedness. This test executes a number of checks 2.45 + * in order to establish as to whether an instantiation of a generic type conforms 2.46 + * to the generic class' declared bounds. 2.47 + */ 2.48 +public class GenericTypeWellFormednessTest extends TypeHarness { 2.49 + 2.50 + static int executedCount = 0; 2.51 + static int ignoredCount = 0; 2.52 + 2.53 + InstantiableType[] rows; 2.54 + Type[] columns; 2.55 + 2.56 + static class InstantiableType { 2.57 + protected Type type; 2.58 + 2.59 + public InstantiableType(Type type) { 2.60 + this.type = type; 2.61 + } 2.62 + 2.63 + Type inst(Type clazz) { 2.64 + return type; 2.65 + } 2.66 + } 2.67 + 2.68 + enum Result { 2.69 + /* generic type is well-formed w.r.t. declared bounds */ 2.70 + OK(true), 2.71 + /* generic type is not well-formed w.r.t. declared bounds */ 2.72 + FAIL(false), 2.73 + /* generic type is not well-formed w.r.t. declared bounds according to the JLS 3rd, 2.74 + * but javac allows it (spec for generic type well-formedness is overly restrictive) 2.75 + * See regression test test/tools/generics/wildcards/T5097548.java 2.76 + */ 2.77 + IGNORE(false); 2.78 + 2.79 + boolean value; 2.80 + 2.81 + Result(boolean value) { 2.82 + this.value = value; 2.83 + } 2.84 + } 2.85 + 2.86 + static final Result T = Result.OK; 2.87 + static final Result F = Result.FAIL; 2.88 + static final Result I = Result.IGNORE; 2.89 + 2.90 + /*is a type in 'rows' a valid instantiation for the generic class in 'col' ? */ 2.91 + Result[][] isValidInstantiation = { 2.92 + //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<?>> 2.93 + /*Foo<Object>*/ { T , T , F , F , F , F , F }, 2.94 + /*Foo<Number>*/ { T , T , T , F , F , F , F }, 2.95 + /*Foo<Integer>*/ { T , T , T , F , F , F , F }, 2.96 + /*Foo<Double>*/ { T , T , T , F , F , F , F }, 2.97 + /*Foo<String>*/ { T , T , F , F , F , F , F }, 2.98 + /*Foo<X1>*/ { T , T , F , F , F , F , F }, 2.99 + /*Foo<X2>*/ { T , T , T , F , F , F , F }, 2.100 + /*Foo<X3>*/ { T , T , T , F , F , F , F }, 2.101 + /*Foo<X4>*/ { T , T , T , F , F , F , F }, 2.102 + /*Foo<X5>*/ { T , T , F , F , F , F , F }, 2.103 + /*Foo<X6>*/ { T , T , F , T , T , T , T }, 2.104 + /*Foo<+Object>*/ { T , T , I , I , I , I , I }, 2.105 + /*Foo<+Number>*/ { T , T , T , F , F , F , F }, 2.106 + /*Foo<+Integer>*/{ T , T , T , F , F , F , F }, 2.107 + /*Foo<+Double>*/ { T , T , T , F , F , F , F }, 2.108 + /*Foo<+String>*/ { T , T , F , F , F , F , F }, 2.109 + /*Foo<+X1>*/ { T , T , F , F , F , F , F }, 2.110 + /*Foo<+X2>*/ { T , T , T , F , F , F , F }, 2.111 + /*Foo<+X3>*/ { T , T , T , F , F , F , F }, 2.112 + /*Foo<+X4>*/ { T , T , T , F , F , F , F }, 2.113 + /*Foo<+X5>*/ { T , T , F , F , F , F , F }, 2.114 + /*Foo<+X6>*/ { T , T , F , T , T , I , T }, 2.115 + /*Foo<-Object>*/ { T , T , F , F , F , F , F }, 2.116 + /*Foo<-Number>*/ { T , T , T , F , F , F , F }, 2.117 + /*Foo<-Integer>*/{ T , T , T , F , F , F , F }, 2.118 + /*Foo<-Double>*/ { T , T , T , F , F , F , F }, 2.119 + /*Foo<-String>*/ { T , T , F , F , F , F , F }, 2.120 + /*Foo<-X1>*/ { T , T , I , I , I , I , I }, 2.121 + /*Foo<-X2>*/ { T , T , I , F , F , F , F }, 2.122 + /*Foo<-X3>*/ { T , T , I , F , F , F , F }, 2.123 + /*Foo<-X4>*/ { T , T , I , F , F , F , F }, 2.124 + /*Foo<-X5>*/ { T , T , I , F , F , F , F }, 2.125 + /*Foo<-X6>*/ { T , T , F , T , I , I , T }, 2.126 + /*Foo<?>*/ { T , T , T , T , T , T , T }}; 2.127 + 2.128 + GenericTypeWellFormednessTest() { 2.129 + InstantiableType[] basicTypes = { 2.130 + new InstantiableType(predef.objectType), 2.131 + new InstantiableType(NumberType()), 2.132 + new InstantiableType(box(predef.intType)), 2.133 + new InstantiableType(box(predef.doubleType)), 2.134 + new InstantiableType(predef.stringType) }; 2.135 + 2.136 + InstantiableType[] typeVars = new InstantiableType[basicTypes.length + 1]; 2.137 + for (int i = 0 ; i < basicTypes.length ; i++) { 2.138 + typeVars[i] = new InstantiableType(fac.TypeVariable(basicTypes[i].type)); 2.139 + } 2.140 + typeVars[typeVars.length - 1] = new InstantiableType(null) { 2.141 + Type inst(Type clazz) { 2.142 + TypeVar tvar = fac.TypeVariable(); 2.143 + tvar.bound = subst(clazz, Mapping(clazz.getTypeArguments().head, tvar)); 2.144 + return tvar; 2.145 + } 2.146 + }; 2.147 + 2.148 + InstantiableType[] typeArgs = join(InstantiableType.class, basicTypes, typeVars); 2.149 + 2.150 + InstantiableType[] invariantTypes = new InstantiableType[typeArgs.length]; 2.151 + for (int i = 0 ; i < typeArgs.length ; i++) { 2.152 + final InstantiableType type1 = typeArgs[i]; 2.153 + invariantTypes[i] = new InstantiableType(typeArgs[i].type) { 2.154 + Type inst(Type clazz) { 2.155 + return subst(clazz, Mapping(clazz.getTypeArguments().head, type1.inst(clazz))); 2.156 + } 2.157 + }; 2.158 + } 2.159 + 2.160 + InstantiableType[] covariantTypes = new InstantiableType[typeArgs.length]; 2.161 + for (int i = 0 ; i < typeArgs.length ; i++) { 2.162 + final InstantiableType type1 = typeArgs[i]; 2.163 + covariantTypes[i] = new InstantiableType(null) { 2.164 + Type inst(Type clazz) { 2.165 + Type t = fac.Wildcard(BoundKind.EXTENDS, type1.inst(clazz)); 2.166 + return subst(clazz, Mapping(clazz.getTypeArguments().head, t)); 2.167 + } 2.168 + }; 2.169 + } 2.170 + 2.171 + InstantiableType[] contravariantTypes = new InstantiableType[typeArgs.length]; 2.172 + for (int i = 0 ; i < typeArgs.length ; i++) { 2.173 + final InstantiableType type1 = typeArgs[i]; 2.174 + contravariantTypes[i] = new InstantiableType(null) { 2.175 + Type inst(Type clazz) { 2.176 + Type t = fac.Wildcard(BoundKind.SUPER, type1.inst(clazz)); 2.177 + return subst(clazz, Mapping(clazz.getTypeArguments().head, t)); 2.178 + } 2.179 + }; 2.180 + } 2.181 + 2.182 + InstantiableType[] bivariantTypes = { 2.183 + new InstantiableType(fac.Wildcard(BoundKind.UNBOUND, predef.objectType)) { 2.184 + Type inst(Type clazz) { 2.185 + return subst(clazz, Mapping(clazz.getTypeArguments().head, type)); 2.186 + } 2.187 + } 2.188 + }; 2.189 + 2.190 + rows = join(InstantiableType.class, invariantTypes, covariantTypes, contravariantTypes, bivariantTypes); 2.191 + 2.192 + Type tv1 = fac.TypeVariable(); 2.193 + Type decl1 = fac.Class(tv1); 2.194 + 2.195 + Type tv2 = fac.TypeVariable(predef.objectType); 2.196 + Type decl2 = fac.Class(tv2); 2.197 + 2.198 + Type tv3 = fac.TypeVariable(NumberType()); 2.199 + Type decl3 = fac.Class(tv3); 2.200 + 2.201 + TypeVar tv4 = fac.TypeVariable(); 2.202 + Type decl4 = fac.Class(tv4); 2.203 + tv4.bound = decl4; 2.204 + tv4.tsym.name = predef.exceptionType.tsym.name; 2.205 + 2.206 + TypeVar tv5 = fac.TypeVariable(); 2.207 + Type decl5 = fac.Class(tv5); 2.208 + tv5.bound = subst(decl5, Mapping(tv5, fac.Wildcard(BoundKind.EXTENDS, tv5))); 2.209 + 2.210 + TypeVar tv6 = fac.TypeVariable(); 2.211 + Type decl6 = fac.Class(tv6); 2.212 + tv6.bound = subst(decl6, Mapping(tv6, fac.Wildcard(BoundKind.SUPER, tv6))); 2.213 + 2.214 + TypeVar tv7 = fac.TypeVariable(); 2.215 + Type decl7 = fac.Class(tv7); 2.216 + tv7.bound = subst(decl7, Mapping(tv7, fac.Wildcard(BoundKind.UNBOUND, predef.objectType))); 2.217 + 2.218 + columns = new Type[] { 2.219 + decl1, decl2, decl3, decl4, decl5, decl6, decl7 2.220 + }; 2.221 + } 2.222 + 2.223 + void test() { 2.224 + for (int i = 0; i < rows.length ; i++) { 2.225 + for (int j = 0; j < columns.length ; j++) { 2.226 + Type decl = columns[j]; 2.227 + Type inst = rows[i].inst(decl); 2.228 + if (isValidInstantiation[i][j] != Result.IGNORE) { 2.229 + executedCount++; 2.230 + assertValidGenericType(inst, isValidInstantiation[i][j].value); 2.231 + } else { 2.232 + ignoredCount++; 2.233 + } 2.234 + } 2.235 + } 2.236 + } 2.237 + 2.238 + Type NumberType() { 2.239 + Symbol s = box(predef.intType).tsym; 2.240 + s.complete(); 2.241 + return ((ClassType)s.type).supertype_field; 2.242 + } 2.243 + 2.244 + @SuppressWarnings("unchecked") 2.245 + <T> T[] join(Class<T> type, T[]... args) { 2.246 + int totalLength = 0; 2.247 + for (T[] arr : args) { 2.248 + totalLength += arr.length; 2.249 + } 2.250 + T[] new_arr = (T[])Array.newInstance(type, totalLength); 2.251 + int idx = 0; 2.252 + for (T[] arr : args) { 2.253 + System.arraycopy(arr, 0, new_arr, idx, arr.length); 2.254 + idx += arr.length; 2.255 + } 2.256 + return new_arr; 2.257 + } 2.258 + 2.259 + public static void main(String[] args) { 2.260 + new GenericTypeWellFormednessTest().test(); 2.261 + System.out.println("Executed checks : " + executedCount); 2.262 + System.out.println("Ignored checks : " + ignoredCount); 2.263 + } 2.264 +}
3.1 --- a/test/tools/javac/types/TypeHarness.java Fri Jan 14 09:45:04 2011 +0000 3.2 +++ b/test/tools/javac/types/TypeHarness.java Fri Jan 14 09:45:52 2011 +0000 3.3 @@ -29,6 +29,7 @@ 3.4 import com.sun.tools.javac.code.Type; 3.5 import com.sun.tools.javac.code.Type.*; 3.6 import com.sun.tools.javac.code.Symbol.*; 3.7 +import com.sun.tools.javac.comp.Check; 3.8 import com.sun.tools.javac.util.List; 3.9 import com.sun.tools.javac.util.ListBuffer; 3.10 import com.sun.tools.javac.util.Name; 3.11 @@ -68,6 +69,7 @@ 3.12 public class TypeHarness { 3.13 3.14 protected Types types; 3.15 + protected Check chk; 3.16 protected Symtab predef; 3.17 protected Names names; 3.18 protected Factory fac; 3.19 @@ -76,6 +78,7 @@ 3.20 Context ctx = new Context(); 3.21 JavacFileManager.preRegister(ctx); 3.22 types = Types.instance(ctx); 3.23 + chk = Check.instance(ctx); 3.24 predef = Symtab.instance(ctx); 3.25 names = Names.instance(ctx); 3.26 fac = new Factory(); 3.27 @@ -157,6 +160,21 @@ 3.28 error(s + msg + t); 3.29 } 3.30 } 3.31 + 3.32 + /** assert that generic type 't' is well-formed */ 3.33 + public void assertValidGenericType(Type t) { 3.34 + assertValidGenericType(t, true); 3.35 + } 3.36 + 3.37 + /** assert that 's' is/is not assignable to 't' */ 3.38 + public void assertValidGenericType(Type t, boolean expected) { 3.39 + if (chk.checkValidGenericType(t) != expected) { 3.40 + String msg = expected ? 3.41 + " is not a valid generic type" : 3.42 + " is a valid generic type"; 3.43 + error(t + msg + " " + t.tsym.type); 3.44 + } 3.45 + } 3.46 // </editor-fold> 3.47 3.48 private void error(String msg) {