mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-20 19:14:38 +02:00
7007432: Test generic types well-formedness
Add a new kind of check (well-formedness of generic type w.r.t. declared bounds) in the type-harness Reviewed-by: jjg
This commit is contained in:
parent
7d90979693
commit
ede9924b4e
3 changed files with 362 additions and 70 deletions
|
@ -506,43 +506,18 @@ public class Check {
|
|||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 !types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Check that type is different from 'void'.
|
||||
|
@ -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
|
||||
* return modifiers together with any implicit modififiers for that symbol.
|
||||
* Warning: we can't use flags() here since this method
|
||||
|
@ -987,11 +1030,17 @@ public class Check {
|
|||
@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 @@ public class Check {
|
|||
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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.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 @@ import com.sun.tools.javac.file.JavacFileManager;
|
|||
public class TypeHarness {
|
||||
|
||||
protected Types types;
|
||||
protected Check chk;
|
||||
protected Symtab predef;
|
||||
protected Names names;
|
||||
protected Factory fac;
|
||||
|
@ -76,6 +78,7 @@ public class TypeHarness {
|
|||
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 @@ public class TypeHarness {
|
|||
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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue