changeset 2008:4c83889e32c8

8012238: Nested method capture and inference
author mcimadamore
date Tue, 16 Apr 2013 15:42:29 +0100
parents e16815044b0b
children f8c62319e8b8
files src/share/classes/com/sun/tools/javac/code/Type.java src/share/classes/com/sun/tools/javac/comp/Attr.java src/share/classes/com/sun/tools/javac/comp/Infer.java src/share/classes/com/sun/tools/javac/comp/Resolve.java test/tools/javac/lambda/NestedCapture01.java test/tools/javac/lambda/NestedCapture02.java test/tools/javac/lambda/NestedCapture03.java
diffstat 7 files changed, 257 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/tools/javac/code/Type.java	Fri Apr 12 15:38:08 2013 +0100
+++ b/src/share/classes/com/sun/tools/javac/code/Type.java	Tue Apr 16 15:42:29 2013 +0100
@@ -1333,7 +1333,7 @@
         }
 
         /** inference variable bounds */
-        private Map<InferenceBound, List<Type>> bounds;
+        protected Map<InferenceBound, List<Type>> bounds;
 
         /** inference variable's inferred type (set from Infer.java) */
         public Type inst = null;
@@ -1390,7 +1390,11 @@
         }
 
         /** add a bound of a given kind - this might trigger listener notification */
-        public void addBound(InferenceBound ib, Type bound, Types types) {
+        public final void addBound(InferenceBound ib, Type bound, Types types) {
+            addBound(ib, bound, types, false);
+        }
+        
+        public void addBound(InferenceBound ib, Type bound, Types types, boolean update) {
             Type bound2 = toTypeVarMap.apply(bound);
             List<Type> prevBounds = bounds.get(ib);
             for (Type b : prevBounds) {
@@ -1445,7 +1449,7 @@
                     bounds.put(ib, newBounds.toList());
                     //step 3 - for each dependency, add new replaced bound
                     for (Type dep : deps) {
-                        addBound(ib, types.subst(dep, from, to), types);
+                        addBound(ib, types.subst(dep, from, to), types, true);
                     }
                 }
             } finally {
@@ -1461,6 +1465,39 @@
                 listener.varChanged(this, ibs);
             }
         }
+        
+        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, Types types) {
+            super(origin, types);
+            if (!origin.lower.hasTag(BOT)) {
+                bounds.put(InferenceBound.LOWER, List.of(origin.lower));
+            }
+        }
+
+        @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);
+            }
+        }
+
+        @Override
+        public boolean isCaptured() {
+            return true;
+        }
     }
 
     /** Represents VOID or NONE.
--- a/src/share/classes/com/sun/tools/javac/comp/Attr.java	Fri Apr 12 15:38:08 2013 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Attr.java	Tue Apr 16 15:42:29 2013 +0100
@@ -2689,7 +2689,7 @@
                 result = that.type = target;
                 return;
             }
-
+            
             if (resultInfo.checkContext.deferredAttrContext().mode == AttrMode.CHECK) {
 
                 if (!that.kind.isUnbound() &&
@@ -4254,9 +4254,7 @@
         }
 
     private Type capture(Type type) {
-        //do not capture free types
-        return resultInfo.checkContext.inferenceContext().free(type) ?
-                type : types.capture(type);
+        return types.capture(type);
     }
 
     private void validateTypeAnnotations(JCTree tree) {
--- a/src/share/classes/com/sun/tools/javac/comp/Infer.java	Fri Apr 12 15:38:08 2013 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Infer.java	Tue Apr 16 15:42:29 2013 +0100
@@ -159,7 +159,8 @@
                     !warn.hasNonSilentLint(Lint.LintCategory.UNCHECKED)) {
                 //inject return constraints earlier
                 checkWithinBounds(inferenceContext, warn); //propagation
-                generateReturnConstraints(resultInfo, mt, inferenceContext);
+                Type newRestype = generateReturnConstraints(resultInfo, mt, inferenceContext);
+                mt = (MethodType)types.createMethodTypeWithReturn(mt, newRestype);
                 //propagate outwards if needed
                 if (resultInfo.checkContext.inferenceContext().free(resultInfo.pt)) {
                     //propagate inference context outwards and exit
@@ -209,9 +210,20 @@
      * call occurs in a context where a type T is expected, use the expected
      * type to derive more constraints on the generic method inference variables.
      */
-    void generateReturnConstraints(Attr.ResultInfo resultInfo,
+    Type generateReturnConstraints(Attr.ResultInfo resultInfo,
             MethodType mt, InferenceContext inferenceContext) {
-        Type qtype1 = inferenceContext.asFree(mt.getReturnType());
+        Type from = mt.getReturnType();
+        if (mt.getReturnType().containsAny(inferenceContext.inferencevars) &&
+                resultInfo.checkContext.inferenceContext() != emptyContext) {
+            from = types.capture(from);
+            //add synthetic captured ivars
+            for (Type t : from.getTypeArguments()) {
+                if (t.hasTag(TYPEVAR) && ((TypeVar)t).isCaptured()) {
+                    inferenceContext.addVar((TypeVar)t);
+                }
+            }
+        }
+        Type qtype1 = inferenceContext.asFree(from);
         Type to = returnConstraintTarget(qtype1, resultInfo.pt);
         Assert.check(allowGraphInference || !resultInfo.checkContext.inferenceContext().free(to),
                 "legacy inference engine cannot handle constraints on both sides of a subtyping assertion");
@@ -224,6 +236,7 @@
                     .setMessage("infer.no.conforming.instance.exists",
                     inferenceContext.restvars(), mt.getReturnType(), to);
         }
+        return from;
     }
     //where
         private Type returnConstraintTarget(Type from, Type to) {
@@ -435,7 +448,9 @@
                     EnumSet<IncorporationStep> incorporationSteps = allowGraphInference ?
                             incorporationStepsGraph : incorporationStepsLegacy;
                     for (IncorporationStep is : incorporationSteps) {
-                        is.apply(uv, inferenceContext, warn);
+                        if (is.accepts(uv, inferenceContext)) {
+                            is.apply(uv, inferenceContext, warn);
+                        }
                     }
                 }
                 if (!mlistener.changed || !allowGraphInference) break;
@@ -523,6 +538,11 @@
                     }
                 }
             }
+            @Override
+            boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
+                //applies to all undetvars
+                return true;
+            }
         },
         /**
          * Check consistency of equality constraints. This is a slightly more aggressive
@@ -558,7 +578,7 @@
          * Check consistency of equality constraints.
          */
         EQ_CHECK() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
+            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {                
                 Infer infer = inferenceContext.infer();
                 for (Type e : uv.getBounds(InferenceBound.EQ)) {
                     if (e.containsAny(inferenceContext.inferenceVars())) continue;
@@ -580,7 +600,7 @@
          * perform {@code S <: T} (which could lead to new bounds).
          */
         CROSS_UPPER_LOWER() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
+            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {                
                 Infer infer = inferenceContext.infer();
                 for (Type b1 : uv.getBounds(InferenceBound.UPPER)) {
                     for (Type b2 : uv.getBounds(InferenceBound.LOWER)) {
@@ -594,7 +614,7 @@
          * perform {@code S <: T} (which could lead to new bounds).
          */
         CROSS_UPPER_EQ() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
+            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {                
                 Infer infer = inferenceContext.infer();
                 for (Type b1 : uv.getBounds(InferenceBound.UPPER)) {
                     for (Type b2 : uv.getBounds(InferenceBound.EQ)) {
@@ -608,7 +628,7 @@
          * perform {@code S <: T} (which could lead to new bounds).
          */
         CROSS_EQ_LOWER() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
+            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {                
                 Infer infer = inferenceContext.infer();
                 for (Type b1 : uv.getBounds(InferenceBound.EQ)) {
                     for (Type b2 : uv.getBounds(InferenceBound.LOWER)) {
@@ -622,7 +642,7 @@
          * perform {@code S == T} (which could lead to new bounds).
          */
         CROSS_EQ_EQ() {
-            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {
+            public void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn) {                
                 Infer infer = inferenceContext.infer();
                 for (Type b1 : uv.getBounds(InferenceBound.EQ)) {
                     for (Type b2 : uv.getBounds(InferenceBound.EQ)) {
@@ -643,6 +663,7 @@
                 for (Type b : uv.getBounds(InferenceBound.UPPER)) {
                     if (inferenceContext.inferenceVars().contains(b)) {
                         UndetVar uv2 = (UndetVar)inferenceContext.asFree(b);
+                        if (uv2.isCaptured()) continue;
                         //alpha <: beta
                         //0. set beta :> alpha
                         uv2.addBound(InferenceBound.LOWER, uv.qtype, infer.types);
@@ -668,6 +689,7 @@
                 for (Type b : uv.getBounds(InferenceBound.LOWER)) {
                     if (inferenceContext.inferenceVars().contains(b)) {
                         UndetVar uv2 = (UndetVar)inferenceContext.asFree(b);
+                        if (uv2.isCaptured()) continue;
                         //alpha :> beta
                         //0. set beta <: alpha
                         uv2.addBound(InferenceBound.UPPER, uv.qtype, infer.types);
@@ -693,6 +715,7 @@
                 for (Type b : uv.getBounds(InferenceBound.EQ)) {
                     if (inferenceContext.inferenceVars().contains(b)) {
                         UndetVar uv2 = (UndetVar)inferenceContext.asFree(b);
+                        if (uv2.isCaptured()) continue;
                         //alpha == beta
                         //0. set beta == alpha
                         uv2.addBound(InferenceBound.EQ, uv.qtype, infer.types);
@@ -718,6 +741,10 @@
         };
 
         abstract void apply(UndetVar uv, InferenceContext inferenceContext, Warner warn);
+        
+        boolean accepts(UndetVar uv, InferenceContext inferenceContext) {
+            return !uv.isCaptured();
+        }
     }
 
     /** incorporation steps to be executed when running in legacy mode */
@@ -970,7 +997,7 @@
         THROWS(InferenceBound.UPPER) {
             @Override
             public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
-                if ((t.qtype.tsym.flags() & Flags.THROWS) == 0) {
+                if (t.isCaptured() || (t.qtype.tsym.flags() & Flags.THROWS) == 0) {
                     //not a throws undet var
                     return false;
                 }
@@ -1023,13 +1050,36 @@
         UPPER_LEGACY(InferenceBound.UPPER) {
             @Override
             public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
-                return !inferenceContext.free(t.getBounds(ib));
+                return !inferenceContext.free(t.getBounds(ib)) && !t.isCaptured();
             }
 
             @Override
             Type solve(UndetVar uv, InferenceContext inferenceContext) {
                 return UPPER.solve(uv, inferenceContext);
             }
+        },
+        /**
+         * Like the former; the only difference is that this step can only be applied
+         * if all upper/lower bounds are ground.
+         */
+        CAPTURED(InferenceBound.UPPER) {
+            @Override
+            public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
+                return !inferenceContext.free(t.getBounds(InferenceBound.UPPER, InferenceBound.LOWER));
+            }
+
+            @Override
+            Type solve(UndetVar uv, InferenceContext inferenceContext) {
+                Infer infer = inferenceContext.infer();
+                Type upper = UPPER.filterBounds(uv, inferenceContext).nonEmpty() ?
+                        UPPER.solve(uv, inferenceContext) :
+                        infer.syms.objectType;
+                Type lower = LOWER.filterBounds(uv, inferenceContext).nonEmpty() ?
+                        LOWER.solve(uv, inferenceContext) :
+                        infer.syms.botType;
+                CapturedType prevCaptured = (CapturedType)uv.qtype;
+                return new CapturedType(prevCaptured.tsym.name, prevCaptured.tsym.owner, upper, lower, prevCaptured.wildcard);
+            }
         };
 
         final InferenceBound ib;
@@ -1048,7 +1098,7 @@
          * Can the inference variable be instantiated using this step?
          */
         public boolean accepts(UndetVar t, InferenceContext inferenceContext) {
-            return filterBounds(t, inferenceContext).nonEmpty();
+            return filterBounds(t, inferenceContext).nonEmpty() && !t.isCaptured();
         }
 
         /**
@@ -1085,7 +1135,7 @@
 
         EQ(EnumSet.of(InferenceStep.EQ)),
         EQ_LOWER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER)),
-        EQ_LOWER_THROWS_UPPER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER, InferenceStep.THROWS, InferenceStep.UPPER));
+        EQ_LOWER_THROWS_UPPER(EnumSet.of(InferenceStep.EQ, InferenceStep.LOWER, InferenceStep.THROWS, InferenceStep.UPPER, InferenceStep.CAPTURED));
 
         final EnumSet<InferenceStep> steps;
 
@@ -1348,10 +1398,24 @@
             Mapping fromTypeVarFun = new Mapping("fromTypeVarFunWithBounds") {
                 // mapping that turns inference variables into undet vars
                 public Type apply(Type t) {
-                    if (t.hasTag(TYPEVAR)) return new UndetVar((TypeVar)t, types);
-                    else return t.map(this);
+                    if (t.hasTag(TYPEVAR)) {
+                        TypeVar tv = (TypeVar)t;
+                        return tv.isCaptured() ?
+                                new CapturedUndetVar((CapturedType)tv, types) :
+                                new UndetVar(tv, types);
+                    } else {
+                        return t.map(this);
+                    }
                 }
             };
+            
+        /**
+         * add a new inference var to this inference context
+         */
+        void addVar(TypeVar t) {
+            this.undetvars = this.undetvars.prepend(fromTypeVarFun.apply(t));
+            this.inferencevars = this.inferencevars.prepend(t);
+        }
 
         /**
          * returns the list of free variables (as type-variables) in this
--- a/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Fri Apr 12 15:38:08 2013 +0100
+++ b/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Tue Apr 16 15:42:29 2013 +0100
@@ -920,9 +920,22 @@
                 DeferredType dt = (DeferredType)found;
                 return dt.check(this);
             } else {
-                return super.check(pos, chk.checkNonVoid(pos, types.capture(types.upperBound(found.baseType()))));
+                return super.check(pos, chk.checkNonVoid(pos, types.capture(U(found.baseType()))));
             }
         }
+        
+        /**
+         * javac has a long-standing 'simplification' (see 6391995):
+         * given an actual argument type, the method check is performed
+         * on its upper bound. This leads to inconsistencies when an
+         * argument type is checked against itself. For example, given
+         * a type-variable T, it is not true that {@code U(T) <: T},
+         * so we need to guard against that.
+         */
+        private Type U(Type found) {            
+            return found == pt ?
+                    found : types.upperBound(found);
+        }
 
         @Override
         protected MethodResultInfo dup(Type newPt) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/NestedCapture01.java	Tue Apr 16 15:42:29 2013 +0100
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, 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 8012238
+ * @summary Nested method capture and inference
+ * @compile NestedCapture01.java
+ */
+class NestedCapture01 {
+
+    void test(String s) {
+       g(m(s.getClass()));
+    }
+    
+    <F extends String> F m(Class<F> cf) {
+       return null;
+    }
+
+    <P extends String> P g(P vo) {
+       return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/NestedCapture02.java	Tue Apr 16 15:42:29 2013 +0100
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, 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 8012238
+ * @summary Nested method capture and inference
+ * @compile NestedCapture02.java
+ */
+class NestedCapture02<S,T> {
+
+     <S,T> NestedCapture02<S,T> create(NestedCapture02<? super S,?> first,
+             NestedCapture02<? super S, T> second) {
+         return null;
+     }
+
+     <U> NestedCapture02<S, ? extends U> cast(Class<U> target) { return null; }
+     
+     <U> NestedCapture02<S, ? extends U> test(Class<U> target,
+             NestedCapture02<? super S, ?> first, NestedCapture02<? super S, T> second) {
+        return create(first, second.cast(target));
+     }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/lambda/NestedCapture03.java	Tue Apr 16 15:42:29 2013 +0100
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013, 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 8012238
+ * @summary Nested method capture and inference
+ * @compile NestedCapture03.java
+ */
+class NestedCapture03 {
+    <T extends String> T factory(Class<T> c) { return null; }
+
+    void test(Class<?> c) {
+        factory(c.asSubclass(String.class)).matches(null);
+    }
+}