提交 457dffde 编写于 作者: M mcimadamore

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
上级 b51bae99
...@@ -506,43 +506,18 @@ public class Check { ...@@ -506,43 +506,18 @@ public class Check {
* @param a The type that should be bounded by bs. * @param a The type that should be bounded by bs.
* @param bs The bound. * @param bs The bound.
*/ */
private void checkExtends(DiagnosticPosition pos, Type a, TypeVar bs) { private boolean checkExtends(Type a, TypeVar bs) {
if (a.isUnbound()) { if (a.isUnbound()) {
return; return true;
} else if (a.tag != WILDCARD) { } else if (a.tag != WILDCARD) {
a = types.upperBound(a); a = types.upperBound(a);
for (List<Type> l = types.getBounds(bs); l.nonEmpty(); l = l.tail) { return types.isSubtype(a, bs.bound);
if (!types.isSubtype(a, l.head)) {
log.error(pos, "not.within.bounds", a);
return;
}
}
} else if (a.isExtendsBound()) { } else if (a.isExtendsBound()) {
if (!types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings)) return types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings);
log.error(pos, "not.within.bounds", a);
} else if (a.isSuperBound()) { } else if (a.isSuperBound()) {
if (types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound())) return !types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound());
log.error(pos, "not.within.bounds", a);
} }
} return true;
/** 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;
}
} }
/** Check that type is different from 'void'. /** Check that type is different from 'void'.
...@@ -775,6 +750,74 @@ public class Check { ...@@ -775,6 +750,74 @@ public class Check {
} }
} }
/**
* 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 /** Check that given modifiers are legal for given symbol and
* return modifiers together with any implicit modififiers for that symbol. * return modifiers together with any implicit modififiers for that symbol.
* Warning: we can't use flags() here since this method * Warning: we can't use flags() here since this method
...@@ -987,11 +1030,17 @@ public class Check { ...@@ -987,11 +1030,17 @@ public class Check {
@Override @Override
public void visitTypeApply(JCTypeApply tree) { public void visitTypeApply(JCTypeApply tree) {
if (tree.type.tag == CLASS) { 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<JCExpression> args = tree.arguments;
List<Type> forms = tree.type.tsym.type.getTypeArguments(); 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; boolean is_java_lang_Class = tree.type.tsym.flatName() == names.java_lang_Class;
...@@ -1001,46 +1050,10 @@ public class Check { ...@@ -1001,46 +1050,10 @@ public class Check {
validateTree(args.head, validateTree(args.head,
!(isOuter && is_java_lang_Class), !(isOuter && is_java_lang_Class),
false); 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; args = args.tail;
forms = forms.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 // Check that this type is either fully parameterized, or
// not parameterized at all. // not parameterized at all.
if (tree.type.getEnclosingType().isRaw()) if (tree.type.getEnclosingType().isRaw())
......
/*
* 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);
}
}
...@@ -29,6 +29,7 @@ import com.sun.tools.javac.code.Symtab; ...@@ -29,6 +29,7 @@ import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type;
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 com.sun.tools.javac.comp.Check;
import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Name;
...@@ -68,6 +69,7 @@ import com.sun.tools.javac.file.JavacFileManager; ...@@ -68,6 +69,7 @@ import com.sun.tools.javac.file.JavacFileManager;
public class TypeHarness { public class TypeHarness {
protected Types types; protected Types types;
protected Check chk;
protected Symtab predef; protected Symtab predef;
protected Names names; protected Names names;
protected Factory fac; protected Factory fac;
...@@ -76,6 +78,7 @@ public class TypeHarness { ...@@ -76,6 +78,7 @@ public class TypeHarness {
Context ctx = new Context(); Context ctx = new Context();
JavacFileManager.preRegister(ctx); JavacFileManager.preRegister(ctx);
types = Types.instance(ctx); types = Types.instance(ctx);
chk = Check.instance(ctx);
predef = Symtab.instance(ctx); predef = Symtab.instance(ctx);
names = Names.instance(ctx); names = Names.instance(ctx);
fac = new Factory(); fac = new Factory();
...@@ -157,6 +160,21 @@ public class TypeHarness { ...@@ -157,6 +160,21 @@ public class TypeHarness {
error(s + msg + t); 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> // </editor-fold>
private void error(String msg) { private void error(String msg) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册