changeset 2518:f0d0dd18b996

Unify code paths for type-argument bound-checking. * All bound-checking code is now done in Check - this includes generic type well-formedness AND explicit method type-argument checks * Completely overhauled logic for bound check. Code is now more general and reusable * Changed source/target version of bootstrap compiler to allow lambda support
author mcimadamore
date Tue, 15 Jul 2014 18:37:29 +0100
parents d9c7704baf6e
children a82e61eca30b
files make/build.properties src/share/classes/com/sun/tools/javac/comp/Attr.java src/share/classes/com/sun/tools/javac/comp/Check.java src/share/classes/com/sun/tools/javac/comp/Resolve.java src/share/classes/com/sun/tools/javac/util/List.java src/share/classes/com/sun/tools/javac/util/Pair.java test/tools/javac/generics/typevars/6968793/T6968793.out
diffstat 7 files changed, 187 insertions(+), 129 deletions(-) [+]
line wrap: on
line diff
--- a/make/build.properties	Fri Jul 11 17:05:17 2014 +0100
+++ b/make/build.properties	Tue Jul 15 18:37:29 2014 +0100
@@ -32,8 +32,8 @@
 # boot.java.home = /opt/jdk/1.7.0
 boot.java = ${boot.java.home}/bin/java
 boot.javac = ${boot.java.home}/bin/javac
-boot.javac.source = 7
-boot.javac.target = 7
+boot.javac.source = 8
+boot.javac.target = 8
 
 # This is the JDK used to run the product version of the tools,
 # for example, for testing. If you're building a complete JDK, specify that.
--- a/src/share/classes/com/sun/tools/javac/comp/Attr.java	Fri Jul 11 17:05:17 2014 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Attr.java	Tue Jul 15 18:37:29 2014 +0100
@@ -4086,7 +4086,7 @@
         Type clazztype = chk.checkClassType(tree.clazz.pos(), attribType(tree.clazz, env));
 
         // Attribute type parameters
-        List<Type> actuals = attribTypes(tree.arguments, env);
+        List<Type> actuals = attribAnyTypes(tree.arguments, env);
 
         if (clazztype.hasTag(CLASS)) {
             List<Type> formals = clazztype.tsym.type.getTypeArguments();
--- a/src/share/classes/com/sun/tools/javac/comp/Check.java	Fri Jul 11 17:05:17 2014 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Check.java	Tue Jul 15 18:37:29 2014 +0100
@@ -26,6 +26,7 @@
 package com.sun.tools.javac.comp;
 
 import java.util.*;
+import java.util.function.Function;
 
 import javax.tools.JavaFileManager;
 
@@ -623,27 +624,6 @@
 
         private static final boolean ignoreAnnotatedCasts = true;
 
-    /** Check that a type is within some bounds.
-     *
-     *  Used in TypeApply to verify that, e.g., X in {@code V<X>} is a valid
-     *  type argument.
-     *  @param a             The type that should be bounded by bs.
-     *  @param bound         The bound.
-     */
-    private boolean checkExtends(Type a, Type bound) {
-         if (a.isUnbound()) {
-             return true;
-         } else if (!a.hasTag(WILDCARD)) {
-             a = types.cvarUpperBound(a);
-             return types.isSubtype(a, bound);
-         } else if (a.isExtendsBound()) {
-             return types.isCastable(bound, types.wildUpperBound(a), types.noWarnings);
-         } else if (a.isSuperBound()) {
-             return !types.notSoftSubtype(types.wildLowerBound(a), bound);
-         }
-         return true;
-     }
-
     /** Check that type is different from 'void'.
      *  @param pos           Position to be used for error reporting.
      *  @param t             The type to be checked.
@@ -955,92 +935,170 @@
      * @return true if 't' is well-formed
      */
     public boolean checkValidGenericType(Type t) {
-        return firstIncompatibleTypeArg(t) == null;
+        BoundChecker<Void> bc = new ClassBoundChecker<>(t);
+        return bc.checkBounds();
     }
-    //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> bounds_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 bounds where
-                // type-parameters are replaced with actuals argument types.
-                bounds_buf.append(types.subst(forms.head.getUpperBound(), formals, actuals));
-                args = args.tail;
-                forms = forms.tail;
+
+    /**
+     * Handler for bound check analysis. This interface defines an entry-point to allow clients
+     * to associate custom behavior to a bound check failure.
+     */
+    @FunctionalInterface
+    interface BoundCheckHandler<A> {
+        /** entry point for handling bound check failures. */
+        void onFailure(Type actual, Type formal, A optArg);
+
+        /** no-op handler. */
+        static BoundCheckHandler<?> dummyHandler = (x,y,z) -> { };
+    }
+
+    /**
+     * This class defines the logic for checking as to whether a list of actual type-arguments
+     * is well-formed w.r.t. corresponding type-variables. The checker can also be passed
+     * optional extra arguments (i.e. diagnostic positions) to be associated with actual type-arguments.
+     * This is useful i.e. when generating error messages corresponding to a given bound check failure.
+     */
+    class BoundChecker<A> {
+
+        /** actual type-arguments. */
+        List<Type> actuals;
+
+        /** formal type-arguments (type-vars). */
+        List<Type> formals;
+
+        /** list of optional arguments associated with actuals. */
+        List<A> optArgs;
+
+        /** handler for boun check failures. */
+        BoundCheckHandler<A> handler;
+
+        BoundChecker(List<Type> actuals, List<Type> formals) {
+            this(actuals, formals, List.fill(actuals.length(), null));
+        }
+
+        @SuppressWarnings("unchecked")
+        BoundChecker(List<Type> actuals, List<Type> formals, List<A> optArgs) {
+            this.actuals = actuals;
+            this.formals = formals;
+            this.optArgs = optArgs;
+            this.handler = (BoundCheckHandler<A>)BoundCheckHandler.dummyHandler;
+        }
+
+        /**
+         * Attach an bouncd check handler.
+         */
+        BoundChecker<A> onFailure(BoundCheckHandler<A> onFailure) {
+            this.handler = onFailure;
+            return this;
+        }
+
+        /**
+         * Replace formals for actuals type-arguments in a given type (bound).
+         */
+        Type replaceBound(Type bound) {
+            return types.subst(bound, formals, actuals);
+        }
+
+        /**
+         * Perform the bound check. This routine takes care of associating actuals with optional
+         * arguments and to call the handler whenever a bound check failure is found.
+         */
+        boolean checkBounds() {
+            return Pair.zip(Pair.zip(actuals, optArgs), formals).stream()
+                    .map(ts -> new Pair<>(ts.fst, new Pair<>(ts.snd, replaceBound(ts.snd.getUpperBound()))))
+                    .filter(ts -> !isTypeArgErroneous(ts.fst.fst) && !ts.snd.snd.isErroneous())
+                    .filter(ts -> !checkBound(ts.fst.fst, ts.snd.snd))
+                    .peek(err -> handler.onFailure(err.fst.fst, err.snd.fst, err.fst.snd))
+                    .count() == 0;
+        }
+
+        /**
+         *  Check that a type is well-formed w.r.t. its bound.
+         */
+        boolean checkBound(Type a, Type bound) {
+            if (a.isUnbound()) {
+                return true;
+            } else if (!a.hasTag(WILDCARD)) {
+                a = types.cvarUpperBound(a);
+                return types.isSubtype(a, bound);
+            } else if (a.isExtendsBound()) {
+                return types.isCastable(bound, types.wildUpperBound(a), types.noWarnings);
+            } else if (a.isSuperBound()) {
+                return !types.notSoftSubtype(types.wildLowerBound(a), bound);
             }
-
-            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> bounds = bounds_buf.toList();
-
-            while (args.nonEmpty() && bounds.nonEmpty()) {
-                Type actual = args.head;
-                if (!isTypeArgErroneous(actual) &&
-                        !bounds.head.isErroneous() &&
-                        !checkExtends(actual, bounds.head)) {
-                    return args.head;
-                }
-                args = args.tail;
-                bounds = bounds.tail;
-            }
-
-            args = type.getTypeArguments();
-            bounds = bounds_buf.toList();
-
-            for (Type arg : types.capture(type).getTypeArguments()) {
-                if (arg.hasTag(TYPEVAR) &&
-                        arg.getUpperBound().isErroneous() &&
-                        !bounds.head.isErroneous() &&
-                        !isTypeArgErroneous(args.head)) {
-                    return args.head;
-                }
-                bounds = bounds.tail;
-                args = args.tail;
-            }
-
-            return null;
+            return true;
+        }
+
+        /** predicate method: does the type 't' contains any error type? */
+        private boolean isTypeArgErroneous(Type t) {
+            return isTypeArgErroneous.visit(t);
         }
         //where
-        boolean isTypeArgErroneous(Type t) {
-            return isTypeArgErroneous.visit(t);
+            Types.UnaryVisitor<Boolean> isTypeArgErroneous = new Types.UnaryVisitor<Boolean>() {
+                public Boolean visitType(Type t, Void s) {
+                    return t.isErroneous();
+                }
+                @Override
+                public Boolean visitTypeVar(TypeVar t, Void s) {
+                    return visit(t.getUpperBound());
+                }
+                @Override
+                public Boolean visitCapturedType(CapturedType t, Void s) {
+                    return visit(t.getUpperBound()) ||
+                            visit(t.getLowerBound());
+                }
+                @Override
+                public Boolean visitWildcardType(WildcardType t, Void s) {
+                    return visit(t.type);
+                }
+            };
+    }
+
+    /**
+     * This class defines custom bound checks for class types. Extra logic needs to be added in
+     * order to handle with capture-conversion related failures.
+     */
+    class ClassBoundChecker<A> extends BoundChecker<A> {
+
+        /** class type */
+        Type ctype;
+
+        /** a map from actual type-arguments to captured type-arguments */
+        LinkedHashMap<Type, Type> capturedActuals = new LinkedHashMap<>();
+
+        ClassBoundChecker(Type ctype) {
+            this(ctype, List.fill(ctype.getTypeArguments().length(), null));
         }
 
-        Types.UnaryVisitor<Boolean> isTypeArgErroneous = new Types.UnaryVisitor<Boolean>() {
-            public Boolean visitType(Type t, Void s) {
-                return t.isErroneous();
-            }
-            @Override
-            public Boolean visitTypeVar(TypeVar t, Void s) {
-                return visit(t.getUpperBound());
-            }
-            @Override
-            public Boolean visitCapturedType(CapturedType t, Void s) {
-                return visit(t.getUpperBound()) ||
-                        visit(t.getLowerBound());
-            }
-            @Override
-            public Boolean visitWildcardType(WildcardType t, Void s) {
-                return visit(t.type);
-            }
-        };
+        ClassBoundChecker(Type ctype, List<A> optArgs) {
+            super(ctype.getTypeArguments(), ctype.tsym.type.getTypeArguments(), optArgs);
+            this.ctype = ctype;
+            Pair.zip(actuals, types.capture(ctype).getTypeArguments())
+                    .forEach(ts -> capturedActuals.put(ts.fst, ts.snd));
+        }
+
+        @Override
+        Type replaceBound(Type bound) {
+            return types.subst(bound, ctype.tsym.type.allparams(), ctype.allparams());
+        }
+
+        @Override
+        protected boolean checkBound(Type actual, Type bound) {
+            Type capturedActual = capturedActuals.get(actual);
+            return super.checkBound(actual, bound) &&
+                    (!capturedActual.hasTag(TYPEVAR) || !capturedActual.getUpperBound().isErroneous());
+        }
+
+        @Override
+        boolean checkBounds() {
+            //First, let the actual arguments know their bound
+            List<Type> tvars_cap = types.substBounds(formals,
+                    ctype.tsym.type.allparams(), List.from(capturedActuals.keySet()));
+            Pair.zip(actuals, tvars_cap).forEach(ts -> ts.fst.withTypeVar(ts.snd));
+            //Second, execute bound check
+            return super.checkBounds();
+        }
+    }
 
     /** Check that given modifiers are legal for given symbol and
      *  return modifiers together with any implicit modifiers for that symbol.
@@ -1276,17 +1334,14 @@
                 List<JCExpression> args = tree.arguments;
                 List<Type> forms = tree.type.tsym.type.getTypeArguments();
 
-                Type incompatibleArg = firstIncompatibleTypeArg(tree.type);
-                if (incompatibleArg != null) {
-                    for (JCTree arg : tree.arguments) {
-                        if (arg.type == incompatibleArg) {
-                            log.error(arg, "not.within.bounds", incompatibleArg, forms.head);
-                        }
-                        forms = forms.tail;
-                     }
-                 }
-
-                forms = tree.type.tsym.type.getTypeArguments();
+                BoundChecker<JCExpression> bc = new ClassBoundChecker<>(tree.type, args);
+                bc.onFailure((actual, formal, pos) -> {
+                    if (actual.isPrimitive()) {
+                        checkRefType(pos, actual);
+                    } else {
+                        log.error(pos, "not.within.bounds", actual, formal);
+                    }
+                }).checkBounds();
 
                 boolean is_java_lang_Class = tree.type.tsym.flatName() == names.java_lang_Class;
 
--- a/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Fri Jul 11 17:05:17 2014 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Tue Jul 15 18:37:29 2014 +0100
@@ -532,20 +532,15 @@
             // which is fine, see JLS 15.12.2.1
         } else if (mt.hasTag(FORALL) && typeargtypes.nonEmpty()) {
             ForAll pmt = (ForAll) mt;
+            List<Type> actuals = typeargtypes;
             if (typeargtypes.length() != pmt.tvars.length())
                 throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args
             // Check type arguments are within bounds
-            List<Type> formals = pmt.tvars;
-            List<Type> actuals = typeargtypes;
-            while (formals.nonEmpty() && actuals.nonEmpty()) {
-                List<Type> bounds = types.subst(types.getBounds((TypeVar)formals.head),
-                                                pmt.tvars, typeargtypes);
-                for (; bounds.nonEmpty(); bounds = bounds.tail)
-                    if (!types.isSubtypeUnchecked(actuals.head, bounds.head, warn))
-                        throw inapplicableMethodException.setMessage("explicit.param.do.not.conform.to.bounds",actuals.head, bounds);
-                formals = formals.tail;
-                actuals = actuals.tail;
-            }
+            Check.BoundChecker<Void> bc = chk.new BoundChecker<>(typeargtypes, pmt.tvars);
+            bc.onFailure((actual, formal, _unused) -> {
+                throw inapplicableMethodException.setMessage("explicit.param.do.not.conform.to.bounds",
+                        actual, types.getBounds((TypeVar)formal).stream().map(t->bc.replaceBound(t)));
+            }).checkBounds();
             mt = types.subst(pmt.qtype, pmt.tvars, typeargtypes);
         } else if (mt.hasTag(FORALL)) {
             ForAll pmt = (ForAll) mt;
--- a/src/share/classes/com/sun/tools/javac/util/List.java	Fri Jul 11 17:05:17 2014 +0100
+++ b/src/share/classes/com/sun/tools/javac/util/List.java	Tue Jul 15 18:37:29 2014 +0100
@@ -178,7 +178,6 @@
      *  @param len    The number of elements in the list.
      *  @param init   The value of each element.
      */
-    @Deprecated
     public static <A> List<A> fill(int len, A init) {
         List<A> l = nil();
         for (int i = 0; i < len; i++) l = new List<>(init, l);
--- a/src/share/classes/com/sun/tools/javac/util/Pair.java	Fri Jul 11 17:05:17 2014 +0100
+++ b/src/share/classes/com/sun/tools/javac/util/Pair.java	Tue Jul 15 18:37:29 2014 +0100
@@ -64,4 +64,14 @@
     public static <A,B> Pair<A,B> of(A a, B b) {
         return new Pair<>(a,b);
     }
+
+    public static <A, B> List<Pair<A, B>> zip(List<A> la, List<B> lb) {
+        ListBuffer<Pair<A,B>> buf = new ListBuffer<>();
+        for (A a : la) {
+            if (lb.isEmpty()) break;
+            buf.append(new Pair<>(a, lb.head));
+            lb = lb.tail;
+        }
+        return buf.toList();
+    }
 }
--- a/test/tools/javac/generics/typevars/6968793/T6968793.out	Fri Jul 11 17:05:17 2014 +0100
+++ b/test/tools/javac/generics/typevars/6968793/T6968793.out	Tue Jul 15 18:37:29 2014 +0100
@@ -1,4 +1,3 @@
 T6968793.java:10:14: compiler.err.not.within.bounds: java.lang.Object, X
-T6968793.java:10:22: compiler.err.not.within.bounds: java.lang.Object, Y
 T6968793.java:10:30: compiler.err.not.within.bounds: java.lang.Object, Z
-3 errors
+2 errors