# HG changeset patch # User mcimadamore # Date 1294998352 0 # Node ID c8d312dd17bce05a0fd1b3bd9c23076cc940a8fb # Parent 2d5aff89aaa34fa7a5aec9839ecd32f066d9b222 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 diff -r 2d5aff89aaa3 -r c8d312dd17bc src/share/classes/com/sun/tools/javac/comp/Check.java --- a/src/share/classes/com/sun/tools/javac/comp/Check.java Fri Jan 14 09:45:04 2011 +0000 +++ b/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 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 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 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 formals = type.tsym.type.allparams(); + List actuals = type.allparams(); + List args = type.getTypeArguments(); + List forms = type.tsym.type.getTypeArguments(); + ListBuffer tvars_buf = new ListBuffer(); + + // 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 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 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 formals = tree.type.tsym.type.allparams(); - List actuals = tree.type.allparams(); List args = tree.arguments; List forms = tree.type.tsym.type.getTypeArguments(); - ListBuffer tvars_buf = new ListBuffer(); + + 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 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 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()) diff -r 2d5aff89aaa3 -r c8d312dd17bc test/tools/javac/types/GenericTypeWellFormednessTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/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, Foo, Foo, Foo>, Foo>, Foo>, Foo> + /*Foo*/ { T , T , F , F , F , F , F }, + /*Foo*/ { T , T , T , F , F , F , F }, + /*Foo*/ { T , T , T , F , F , F , F }, + /*Foo*/ { T , T , T , F , F , F , F }, + /*Foo*/ { T , T , F , F , F , F , F }, + /*Foo*/ { T , T , F , F , F , F , F }, + /*Foo*/ { T , T , T , F , F , F , F }, + /*Foo*/ { T , T , T , F , F , F , F }, + /*Foo*/ { T , T , T , F , F , F , F }, + /*Foo*/ { T , T , F , F , F , F , F }, + /*Foo*/ { 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[] join(Class 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); + } +} diff -r 2d5aff89aaa3 -r c8d312dd17bc test/tools/javac/types/TypeHarness.java --- a/test/tools/javac/types/TypeHarness.java Fri Jan 14 09:45:04 2011 +0000 +++ b/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); + } + } // private void error(String msg) {