changeset 3802:8fc0a7bf47a9

8170410: inference: javac doesn't implement 18.2.5 correctly Summary: javac does not generate constraints of the kind 'throws alpha' as described in the spec Reviewed-by: vromero, dlsmith
author mcimadamore
date Mon, 05 Dec 2016 19:00:56 +0000
parents 07a2dfc18d68
children 586c93260d3b 642eb813070d
files src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java test/tools/javac/generics/inference/8170410/T8170410.java
diffstat 4 files changed, 87 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java	Mon Dec 05 19:42:42 2016 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java	Mon Dec 05 19:00:56 2016 +0000
@@ -1869,6 +1869,12 @@
      */
     public static class UndetVar extends DelegatedType {
 
+        enum Kind {
+            NORMAL,
+            CAPTURED,
+            THROWS;
+        }
+
         /** Inference variable change listener. The listener method is called
          *  whenever a change to the inference variable's bounds occurs
          */
@@ -1929,6 +1935,8 @@
         /** inference variable's change listener */
         public UndetVarListener listener = null;
 
+        Kind kind;
+
         @Override
         public <R,S> R accept(Type.Visitor<R,S> v, S s) {
             return v.visitUndetVar(this, s);
@@ -1937,6 +1945,9 @@
         public UndetVar(TypeVar origin, UndetVarListener listener, Types types) {
             // This is a synthesized internal type, so we cannot annotate it.
             super(UNDETVAR, origin);
+            this.kind = origin.isCaptured() ?
+                    Kind.CAPTURED :
+                    Kind.NORMAL;
             this.listener = listener;
             bounds = new EnumMap<>(InferenceBound.class);
             List<Type> declaredBounds = types.getBounds(origin);
@@ -1948,6 +1959,10 @@
                 //add bound works in reverse order
                 addBound(InferenceBound.UPPER, t, types, true);
             }
+            if (origin.isCaptured() && !origin.lower.hasTag(BOT)) {
+                //add lower bound if needed
+                addBound(InferenceBound.LOWER, origin.lower, types, true);
+            }
         }
 
         @DefinedBy(Api.LANGUAGE_MODEL)
@@ -1977,6 +1992,14 @@
             return result;
         }
 
+        public void setThrow() {
+            if (this.kind == Kind.CAPTURED) {
+                //invalid state transition
+                throw new IllegalStateException();
+            }
+            this.kind = Kind.THROWS;
+        }
+
         /**
          * Returns a new copy of this undet var.
          */
@@ -2062,17 +2085,29 @@
             addBound(ib, bound, types, false);
         }
 
-        protected void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
-            Type bound2 = bound.map(toTypeVarMap).baseType();
-            List<Type> prevBounds = bounds.get(ib);
-            if (bound == qtype) return;
-            for (Type b : prevBounds) {
-                //check for redundancy - use strict version of isSameType on tvars
-                //(as the standard version will lead to false positives w.r.t. clones ivars)
-                if (types.isSameType(b, bound2, true)) return;
+        @SuppressWarnings("fallthrough")
+        private void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
+            if (kind == Kind.CAPTURED && !update) {
+                //Captured inference variables bounds must not be updated during incorporation,
+                //except when some inference variable (beta) has been instantiated in the
+                //right-hand-side of a 'C<alpha> = capture(C<? extends/super beta>) constraint.
+                if (bound.hasTag(UNDETVAR) && !((UndetVar)bound).isCaptured()) {
+                    //If the new incoming bound is itself a (regular) inference variable,
+                    //then we are allowed to propagate this inference variable bounds to it.
+                    ((UndetVar)bound).addBound(ib.complement(), this, types, false);
+                }
+            } else {
+                Type bound2 = bound.map(toTypeVarMap).baseType();
+                List<Type> prevBounds = bounds.get(ib);
+                if (bound == qtype) return;
+                for (Type b : prevBounds) {
+                    //check for redundancy - use strict version of isSameType on tvars
+                    //(as the standard version will lead to false positives w.r.t. clones ivars)
+                    if (types.isSameType(b, bound2, true)) return;
+                }
+                bounds.put(ib, prevBounds.prepend(bound2));
+                notifyBoundChange(ib, bound2, false);
             }
-            bounds.put(ib, prevBounds.prepend(bound2));
-            notifyBoundChange(ib, bound2, false);
         }
         //where
             TypeMapping<Void> toTypeVarMap = new TypeMapping<Void>() {
@@ -2128,46 +2163,12 @@
             }
         }
 
-        public boolean isCaptured() {
-            return false;
-        }
-    }
-
-    /**
-     * This class is used to represent synthetic captured inference variables
-     * that can be generated during nested generic method calls. The only difference
-     * between these inference variables and ordinary ones is that captured inference
-     * variables cannot get new bounds through incorporation.
-     */
-    public static class CapturedUndetVar extends UndetVar {
-
-        public CapturedUndetVar(CapturedType origin, UndetVarListener listener, Types types) {
-            super(origin, listener, types);
-            if (!origin.lower.hasTag(BOT)) {
-                addBound(InferenceBound.LOWER, origin.lower, types, true);
-            }
+        public final boolean isCaptured() {
+            return kind == Kind.CAPTURED;
         }
 
-        @Override
-        public void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
-            if (update) {
-                //only change bounds if request comes from substBounds
-                super.addBound(ib, bound, types, update);
-            }
-            else if (bound.hasTag(UNDETVAR) && !((UndetVar) bound).isCaptured()) {
-                ((UndetVar) bound).addBound(ib.complement(), this, types, false);
-            }
-        }
-
-        @Override
-        public boolean isCaptured() {
-            return true;
-        }
-
-        public UndetVar dup(Types types) {
-            UndetVar uv2 = new CapturedUndetVar((CapturedType)qtype, listener, types);
-            dupTo(uv2, types);
-            return uv2;
+        public final boolean isThrows() {
+            return kind == Kind.THROWS;
         }
     }
 
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Dec 05 19:42:42 2016 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Dec 05 19:00:56 2016 +0000
@@ -2494,6 +2494,11 @@
                     List<Type> thrownTypes = resultInfo.checkContext.inferenceContext().asUndetVars(lambdaType.getThrownTypes());
 
                     chk.unhandled(inferredThrownTypes, thrownTypes);
+
+                    //18.2.5: "In addition, for all j (1 <= j <= n), the constraint reduces to the bound throws Ej"
+                    thrownTypes.stream()
+                            .filter(t -> t.hasTag(UNDETVAR))
+                            .forEach(t -> ((UndetVar)t).setThrow());
                 }
 
                 checkAccessibleTypes(that, localEnv, resultInfo.checkContext.inferenceContext(), lambdaType, currentTarget);
@@ -3074,6 +3079,10 @@
             if (chk.unhandled(refType.getThrownTypes(), thrownTypes).nonEmpty()) {
                 log.error(tree, "incompatible.thrown.types.in.mref", refType.getThrownTypes());
             }
+            //18.2.5: "In addition, for all j (1 <= j <= n), the constraint reduces to the bound throws Ej"
+            thrownTypes.stream()
+                    .filter(t -> t.hasTag(UNDETVAR))
+                    .forEach(t -> ((UndetVar)t).setThrow());
         }
     }
 
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java	Mon Dec 05 19:42:42 2016 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Infer.java	Mon Dec 05 19:00:56 2016 +0000
@@ -627,12 +627,11 @@
     TypeMapping<Void> fromTypeVarFun = new TypeMapping<Void>() {
         @Override
         public Type visitTypeVar(TypeVar tv, Void aVoid) {
-            return new UndetVar(tv, incorporationEngine(), types);
-        }
-
-        @Override
-        public Type visitCapturedType(CapturedType t, Void aVoid) {
-            return new CapturedUndetVar(t, incorporationEngine(), types);
+            UndetVar uv = new UndetVar(tv, incorporationEngine(), types);
+            if ((tv.tsym.flags() & Flags.THROWS) != 0) {
+                uv.setThrow();
+            }
+            return uv;
         }
     };
 
@@ -1463,7 +1462,7 @@
         THROWS(InferenceBound.UPPER) {
             @Override
             public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
-                if ((t.qtype.tsym.flags() & Flags.THROWS) == 0) {
+                if (!t.isThrows()) {
                     //not a throws undet var
                     return false;
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/generics/inference/8170410/T8170410.java	Mon Dec 05 19:00:56 2016 +0000
@@ -0,0 +1,23 @@
+/*
+ * @test
+ * @bug 8170410
+ * @summary inference: javac doesn't implement 18.2.5 correctly
+ * @compile T8170410.java
+ */
+
+class T8170410 {
+    interface CheckedSupplier<T extends Throwable, R> {
+        R get() throws T;
+    }
+
+    static <T extends Throwable, R> CheckedSupplier<T, R> checked(CheckedSupplier<T, R> checkedSupplier) {
+        return checkedSupplier;
+    }
+
+    static void test() {
+        checked(() -> null).get();
+        checked(T8170410::m).get();
+    }
+
+    static String m() { return ""; }
+}