8010822: Intersection type cast for functional expressions does not follow spec EDR
authormcimadamore
Mon, 08 Apr 2013 15:59:29 +0100
changeset 16809 5acfcb821d65
parent 16808 90b98d194b75
child 16810 670d880283e2
8010822: Intersection type cast for functional expressions does not follow spec EDR Summary: Remove support for marker interfaces; redefine intersection type casts to be order-independent Reviewed-by: jjg
langtools/src/share/classes/com/sun/tools/javac/code/Type.java
langtools/src/share/classes/com/sun/tools/javac/code/Types.java
langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java
langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties
langtools/src/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java
langtools/test/tools/javac/diags/examples/NotAnInterfaceComponent.java
langtools/test/tools/javac/diags/examples/SecondaryBoundMustBeMarkerIntf.java
langtools/test/tools/javac/lambda/Intersection01.java
langtools/test/tools/javac/lambda/Intersection01.out
langtools/test/tools/javac/lambda/intersection/IntersectionTargetTypeTest.java
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Type.java	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Type.java	Mon Apr 08 15:59:29 2013 +0100
@@ -908,6 +908,12 @@
             return interfaces_field.prepend(supertype_field);
         }
 
+        public List<Type> getExplicitComponents() {
+            return allInterfaces ?
+                    interfaces_field :
+                    getComponents();
+        }
+
         @Override
         public TypeKind getKind() {
             return TypeKind.INTERSECTION;
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Types.java	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Types.java	Mon Apr 08 15:59:29 2013 +0100
@@ -610,7 +610,7 @@
 
    /**
     * Scope filter used to skip methods that should be ignored (such as methods
-    * overridden by j.l.Object) during function interface conversion/marker interface checks
+    * overridden by j.l.Object) during function interface conversion interface check
     */
     class DescriptorFilter implements Filter<Symbol> {
 
@@ -629,64 +629,6 @@
        }
     };
 
-    // <editor-fold defaultstate="collapsed" desc="isMarker">
-
-    /**
-     * A cache that keeps track of marker interfaces
-     */
-    class MarkerCache {
-
-        private WeakHashMap<TypeSymbol, Entry> _map = new WeakHashMap<TypeSymbol, Entry>();
-
-        class Entry {
-            final boolean isMarkerIntf;
-            final int prevMark;
-
-            public Entry(boolean isMarkerIntf,
-                    int prevMark) {
-                this.isMarkerIntf = isMarkerIntf;
-                this.prevMark = prevMark;
-            }
-
-            boolean matches(int mark) {
-                return  this.prevMark == mark;
-            }
-        }
-
-        boolean get(TypeSymbol origin) throws FunctionDescriptorLookupError {
-            Entry e = _map.get(origin);
-            CompoundScope members = membersClosure(origin.type, false);
-            if (e == null ||
-                    !e.matches(members.getMark())) {
-                boolean isMarkerIntf = isMarkerInterfaceInternal(origin, members);
-                _map.put(origin, new Entry(isMarkerIntf, members.getMark()));
-                return isMarkerIntf;
-            }
-            else {
-                return e.isMarkerIntf;
-            }
-        }
-
-        /**
-         * Is given symbol a marker interface
-         */
-        public boolean isMarkerInterfaceInternal(TypeSymbol origin, CompoundScope membersCache) throws FunctionDescriptorLookupError {
-            return !origin.isInterface() ?
-                    false :
-                    !membersCache.getElements(new DescriptorFilter(origin)).iterator().hasNext();
-        }
-    }
-
-    private MarkerCache markerCache = new MarkerCache();
-
-    /**
-     * Is given type a marker interface?
-     */
-    public boolean isMarkerInterface(Type site) {
-        return markerCache.get(site.tsym);
-    }
-    // </editor-fold>
-
     // <editor-fold defaultstate="collapsed" desc="isSubtype">
     /**
      * Is t an unchecked subtype of s?
@@ -2625,15 +2567,15 @@
     public List<MethodSymbol> interfaceCandidates(Type site, MethodSymbol ms) {
         Filter<Symbol> filter = new MethodFilter(ms, site);
         List<MethodSymbol> candidates = List.nil();
-        for (Symbol s : membersClosure(site, false).getElements(filter)) {
-            if (!site.tsym.isInterface() && !s.owner.isInterface()) {
-                return List.of((MethodSymbol)s);
-            } else if (!candidates.contains(s)) {
-                candidates = candidates.prepend((MethodSymbol)s);
+            for (Symbol s : membersClosure(site, false).getElements(filter)) {
+                if (!site.tsym.isInterface() && !s.owner.isInterface()) {
+                    return List.of((MethodSymbol)s);
+                } else if (!candidates.contains(s)) {
+                    candidates = candidates.prepend((MethodSymbol)s);
+                }
             }
+            return prune(candidates);
         }
-        return prune(candidates);
-    }
 
     public List<MethodSymbol> prune(List<MethodSymbol> methods) {
         ListBuffer<MethodSymbol> methodsMin = ListBuffer.lb();
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Apr 08 15:59:29 2013 +0100
@@ -2273,7 +2273,7 @@
 
             Type lambdaType;
             if (pt() != Type.recoveryType) {
-                target = checkIntersectionTarget(that, target, resultInfo.checkContext);
+                target = targetChecker.visit(target, that);
                 lambdaType = types.findDescriptorType(target);
                 chk.checkFunctionalInterface(that, target);
             } else {
@@ -2281,7 +2281,7 @@
                 lambdaType = fallbackDescriptorType(that);
             }
 
-            setFunctionalInfo(that, pt(), lambdaType, resultInfo.checkContext.inferenceContext());
+            setFunctionalInfo(that, pt(), lambdaType, target, resultInfo.checkContext.inferenceContext());
 
             if (lambdaType.hasTag(FORALL)) {
                 //lambda expression target desc cannot be a generic method
@@ -2396,26 +2396,55 @@
             }
         }
     }
-
-    private Type checkIntersectionTarget(DiagnosticPosition pos, Type pt, CheckContext checkContext) {
-        if (pt != Type.recoveryType && pt.isCompound()) {
-            IntersectionClassType ict = (IntersectionClassType)pt;
-            List<Type> bounds = ict.allInterfaces ?
-                    ict.getComponents().tail :
-                    ict.getComponents();
-            types.findDescriptorType(bounds.head); //propagate exception outwards!
-            for (Type bound : bounds.tail) {
-                if (!types.isMarkerInterface(bound)) {
-                    checkContext.report(pos, diags.fragment("secondary.bound.must.be.marker.intf", bound));
+    //where
+        Types.MapVisitor<DiagnosticPosition> targetChecker = new Types.MapVisitor<DiagnosticPosition>() {
+
+            @Override
+            public Type visitClassType(ClassType t, DiagnosticPosition pos) {
+                return t.isCompound() ?
+                        visitIntersectionClassType((IntersectionClassType)t, pos) : t;
+            }
+
+            public Type visitIntersectionClassType(IntersectionClassType ict, DiagnosticPosition pos) {
+                Symbol desc = types.findDescriptorSymbol(makeNotionalInterface(ict));
+                Type target = null;
+                for (Type bound : ict.getExplicitComponents()) {
+                    TypeSymbol boundSym = bound.tsym;
+                    if (types.isFunctionalInterface(boundSym) &&
+                            types.findDescriptorSymbol(boundSym) == desc) {
+                        target = bound;
+                    } else if (!boundSym.isInterface() || (boundSym.flags() & ANNOTATION) != 0) {
+                        //bound must be an interface
+                        reportIntersectionError(pos, "not.an.intf.component", boundSym);
+                    }
                 }
+                return target != null ?
+                        target :
+                        ict.getExplicitComponents().head; //error recovery
             }
-            //for now (translation doesn't support intersection types)
-            return bounds.head;
-        } else {
-            return pt;
-        }
-    }
-    //where
+
+            private TypeSymbol makeNotionalInterface(IntersectionClassType ict) {
+                ListBuffer<Type> targs = ListBuffer.lb();
+                ListBuffer<Type> supertypes = ListBuffer.lb();
+                for (Type i : ict.interfaces_field) {
+                    if (i.isParameterized()) {
+                        targs.appendList(i.tsym.type.allparams());
+                    }
+                    supertypes.append(i.tsym.type);
+                }
+                IntersectionClassType notionalIntf =
+                        (IntersectionClassType)types.makeCompoundType(supertypes.toList());
+                notionalIntf.allparams_field = targs.toList();
+                notionalIntf.tsym.flags_field |= INTERFACE;
+                return notionalIntf.tsym;
+            }
+
+            private void reportIntersectionError(DiagnosticPosition pos, String key, Object... args) {
+                resultInfo.checkContext.report(pos, diags.fragment("bad.intersection.target.for.functional.expr",
+                        diags.fragment(key, args)));
+            }
+        };
+
         private Type fallbackDescriptorType(JCExpression tree) {
             switch (tree.getTag()) {
                 case LAMBDA:
@@ -2586,7 +2615,7 @@
             Type target;
             Type desc;
             if (pt() != Type.recoveryType) {
-                target = checkIntersectionTarget(that, pt(), resultInfo.checkContext);
+                target = targetChecker.visit(pt(), that);
                 desc = types.findDescriptorType(target);
                 chk.checkFunctionalInterface(that, target);
             } else {
@@ -2594,7 +2623,7 @@
                 desc = fallbackDescriptorType(that);
             }
 
-            setFunctionalInfo(that, pt(), desc, resultInfo.checkContext.inferenceContext());
+            setFunctionalInfo(that, pt(), desc, target, resultInfo.checkContext.inferenceContext());
             List<Type> argtypes = desc.getParameterTypes();
 
             Pair<Symbol, Resolve.ReferenceLookupHelper> refResult =
@@ -2789,19 +2818,24 @@
      * might contain inference variables, we might need to register an hook in the
      * current inference context.
      */
-    private void setFunctionalInfo(final JCFunctionalExpression fExpr, final Type pt, final Type descriptorType, InferenceContext inferenceContext) {
+    private void setFunctionalInfo(final JCFunctionalExpression fExpr, final Type pt,
+            final Type descriptorType, final Type primaryTarget, InferenceContext inferenceContext) {
         if (inferenceContext.free(descriptorType)) {
             inferenceContext.addFreeTypeListener(List.of(pt, descriptorType), new FreeTypeListener() {
                 public void typesInferred(InferenceContext inferenceContext) {
-                    setFunctionalInfo(fExpr, pt, inferenceContext.asInstType(descriptorType), inferenceContext);
+                    setFunctionalInfo(fExpr, pt, inferenceContext.asInstType(descriptorType),
+                            inferenceContext.asInstType(primaryTarget), inferenceContext);
                 }
             });
         } else {
             ListBuffer<TypeSymbol> targets = ListBuffer.lb();
             if (pt.hasTag(CLASS)) {
                 if (pt.isCompound()) {
+                    targets.append(primaryTarget.tsym); //this goes first
                     for (Type t : ((IntersectionClassType)pt()).interfaces_field) {
-                        targets.append(t.tsym);
+                        if (t != primaryTarget) {
+                            targets.append(t.tsym);
+                        }
                     }
                 } else {
                     targets.append(pt.tsym);
--- a/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Mon Apr 08 15:59:29 2013 +0100
@@ -216,9 +216,14 @@
 compiler.misc.no.suitable.functional.intf.inst=\
     cannot infer functional interface descriptor for {0}
 
+# 0: message segment
+compiler.misc.bad.intersection.target.for.functional.expr=\
+    bad intersection type target for lambda or method reference\n\
+    {0}
+
 # 0: type
-compiler.misc.secondary.bound.must.be.marker.intf=\
-    secondary bound {0} must be a marker interface
+compiler.misc.not.an.intf.component=\
+    component type {0} is not an interface
 
 # 0: symbol kind, 1: message segment
 compiler.err.invalid.mref=\
--- a/langtools/src/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java	Mon Apr 08 15:59:29 2013 +0100
@@ -395,6 +395,9 @@
 
         @Override
         public String visitClassSymbol(ClassSymbol s, Locale locale) {
+            if (s.type.isCompound()) {
+                return visit(s.type, locale);
+            }
             String name = nameSimplifier.simplify(s);
             if (name.length() == 0 ||
                     !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
@@ -583,7 +586,11 @@
 
         @Override
         public Void visitClassSymbol(ClassSymbol s, Void ignored) {
-            nameSimplifier.addUsage(s);
+            if (s.type.isCompound()) {
+                typePreprocessor.visit(s.type);
+            } else {
+                nameSimplifier.addUsage(s);
+            }
             return null;
         }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/diags/examples/NotAnInterfaceComponent.java	Mon Apr 08 15:59:29 2013 +0100
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+// key: compiler.err.prob.found.req
+// key: compiler.misc.bad.intersection.target.for.functional.expr
+// key: compiler.misc.not.an.intf.component
+
+class NotAnInterfaceComponent {
+    Object o = (Object & Runnable) ()-> { };
+}
--- a/langtools/test/tools/javac/diags/examples/SecondaryBoundMustBeMarkerIntf.java	Mon Apr 08 15:57:10 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2012, 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.
- */
-
-// key: compiler.err.prob.found.req
-// key: compiler.misc.secondary.bound.must.be.marker.intf
-
-class SecondaryBoundMustBeMarkerInterface {
-    Runnable r = (Runnable & Comparable<?>)()->{};
-}
--- a/langtools/test/tools/javac/lambda/Intersection01.java	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/test/tools/javac/lambda/Intersection01.java	Mon Apr 08 15:59:29 2013 +0100
@@ -25,7 +25,7 @@
  * @test
  * @bug 8002099
  * @summary Add support for intersection types in cast expression
- * @compile/fail/ref=Intersection01.out -XDrawDiagnostics Intersection01.java
+ * @compile Intersection01.java
  */
 class Intersection01 {
 
--- a/langtools/test/tools/javac/lambda/Intersection01.out	Mon Apr 08 15:57:10 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-Intersection01.java:36:45: compiler.err.prob.found.req: (compiler.misc.not.a.functional.intf.1: java.io.Serializable, (compiler.misc.no.abstracts: kindname.interface, java.io.Serializable))
-Intersection01.java:38:45: compiler.err.prob.found.req: (compiler.misc.not.a.functional.intf.1: java.io.Serializable, (compiler.misc.no.abstracts: kindname.interface, java.io.Serializable))
-2 errors
--- a/langtools/test/tools/javac/lambda/intersection/IntersectionTargetTypeTest.java	Mon Apr 08 15:57:10 2013 +0100
+++ b/langtools/test/tools/javac/lambda/intersection/IntersectionTargetTypeTest.java	Mon Apr 08 15:59:29 2013 +0100
@@ -28,10 +28,11 @@
  */
 
 import com.sun.source.util.JavacTask;
-import com.sun.tools.javac.util.List;
 import com.sun.tools.javac.util.ListBuffer;
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import javax.tools.Diagnostic;
 import javax.tools.JavaCompiler;
 import javax.tools.JavaFileObject;
@@ -45,37 +46,45 @@
 
     enum BoundKind {
         INTF,
-        CLASS,
-        SAM,
-        ZAM;
+        CLASS;
     }
 
     enum MethodKind {
-        NONE,
-        ABSTRACT,
-        DEFAULT;
+        NONE(false),
+        ABSTRACT_M(true),
+        DEFAULT_M(false),
+        ABSTRACT_G(true),
+        DEFAULT_G(false);
+
+        boolean isAbstract;
+
+        MethodKind(boolean isAbstract) {
+            this.isAbstract = isAbstract;
+        }
     }
 
     enum TypeKind {
-        A("interface A { }\n", "A", BoundKind.ZAM),
-        B("interface B { default void m() { } }\n", "B", BoundKind.ZAM),
-        C("interface C { void m(); }\n", "C", BoundKind.SAM),
-        D("interface D extends B { }\n", "D", BoundKind.ZAM),
-        E("interface E extends C { }\n", "E", BoundKind.SAM),
-        F("interface F extends C { void g(); }\n", "F", BoundKind.INTF),
-        G("interface G extends B { void g(); }\n", "G", BoundKind.SAM),
-        H("interface H extends A { void g(); }\n", "H", BoundKind.SAM),
+        A("interface A { }\n", "A", BoundKind.INTF, MethodKind.NONE),
+        B("interface B { default void m() { } }\n", "B", BoundKind.INTF, MethodKind.DEFAULT_M),
+        C("interface C { void m(); }\n", "C", BoundKind.INTF, MethodKind.ABSTRACT_M),
+        D("interface D extends B { }\n", "D", BoundKind.INTF, MethodKind.DEFAULT_M),
+        E("interface E extends C { }\n", "E", BoundKind.INTF, MethodKind.ABSTRACT_M),
+        F("interface F extends C { void g(); }\n", "F", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.ABSTRACT_M),
+        G("interface G extends B { void g(); }\n", "G", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.DEFAULT_M),
+        H("interface H extends A { void g(); }\n", "H", BoundKind.INTF, MethodKind.ABSTRACT_G),
         OBJECT("", "Object", BoundKind.CLASS),
         STRING("", "String", BoundKind.CLASS);
 
         String declStr;
         String typeStr;
         BoundKind boundKind;
+        MethodKind[] methodKinds;
 
-        private TypeKind(String declStr, String typeStr, BoundKind boundKind) {
+        private TypeKind(String declStr, String typeStr, BoundKind boundKind, MethodKind... methodKinds) {
             this.declStr = declStr;
             this.typeStr = typeStr;
             this.boundKind = boundKind;
+            this.methodKinds = methodKinds;
         }
 
         boolean compatibleSupertype(TypeKind tk) {
@@ -263,14 +272,22 @@
         boolean errorExpected = !cInfo.wellFormed();
 
         if (ek.isFunctional) {
-            //first bound must be a SAM
-            errorExpected |= cInfo.types[0].boundKind != BoundKind.SAM;
-            if (cInfo.types.length > 1) {
-                //additional bounds must be ZAMs
-                for (int i = 1; i < cInfo.types.length; i++) {
-                    errorExpected |= cInfo.types[i].boundKind != BoundKind.ZAM;
+            List<MethodKind> mks = new ArrayList<>();
+            for (TypeKind tk : cInfo.types) {
+                if (tk.boundKind == BoundKind.CLASS) {
+                    errorExpected = true;
+                    break;
+                } else {
+                    mks = mergeMethods(mks, Arrays.asList(tk.methodKinds));
                 }
             }
+            int abstractCount = 0;
+            for (MethodKind mk : mks) {
+                if (mk.isAbstract) {
+                    abstractCount++;
+                }
+            }
+            errorExpected |= abstractCount != 1;
         }
 
         if (errorExpected != diagChecker.errorFound) {
@@ -281,6 +298,32 @@
         }
     }
 
+    List<MethodKind> mergeMethods(List<MethodKind> l1, List<MethodKind> l2) {
+        List<MethodKind> mergedMethods = new ArrayList<>(l1);
+        for (MethodKind mk2 : l2) {
+            boolean add = !mergedMethods.contains(mk2);
+            switch (mk2) {
+                case ABSTRACT_G:
+                    add = add && !mergedMethods.contains(MethodKind.DEFAULT_G);
+                    break;
+                case ABSTRACT_M:
+                    add = add && !mergedMethods.contains(MethodKind.DEFAULT_M);
+                    break;
+                case DEFAULT_G:
+                    mergedMethods.remove(MethodKind.ABSTRACT_G);
+                case DEFAULT_M:
+                    mergedMethods.remove(MethodKind.ABSTRACT_M);
+                case NONE:
+                    add = false;
+                    break;
+            }
+            if (add) {
+                mergedMethods.add(mk2);
+            }
+        }
+        return mergedMethods;
+    }
+
     static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
 
         boolean errorFound;