Records, Intersection type and lambda

B. Blaser bsrbnd at gmail.com
Sun Dec 27 21:31:04 UTC 2020


Hi Vicente,

On Wed, 23 Dec 2020 at 04:29, Vicente Romero <vicente.romero at oracle.com> wrote:
>
> Hi Bernard,
>
> what side effects do you see?

By tweaking Remi's example:

public class RecLCE {
  interface Foo { }
  interface F<T extends Foo> {
    void call(T t);
  }

  record Bar1() implements Foo { }
  record Bar2() implements Foo { }
  record Pair<P extends Foo>(P p1, P p2) {

    static <P extends Foo> Pair<P> of(P p1, P p2) {
      return new Pair<P>(p1, p2);
    }

    void forEach(F<P> f) {
      f.call(p1);
      f.call(p2);
    }
  }

  static class Hello {
    void m(Foo foo) {
      System.out.println(foo.getClass());
    }
  }

  public static void main(String[] args) {
    var pair = Pair.of(new Bar1(), new Bar2());
    pair.forEach(new Hello()::m);
  }
}

The line you've added is causing the lambda to use the instantiated
type 'Record' which isn't a subtype of 'Foo', see javap's output:
    Method arguments:
      #57 (LRecLCE$Foo;)V
      #58 REF_invokeStatic
RecLCE.lambda$main$0:(LRecLCE$Hello;Ljava/lang/Record;)V
      #61 (Ljava/lang/Record;)V

So, it should use the un-instantiated SAM parameter type 'Foo' instead
which my initial fix was suggesting:
    Method arguments:
      #57 (LRecLCE$Foo;)V
      #58 REF_invokeStatic RecLCE.lambda$main$0:(LRecLCE$Hello;LRecLCE$Foo;)V
      #61 (Ljava/lang/Record;)V

Unfortunately, this very example reveals another issue as the
instantiated type 'Record' is still not convertible to 'Foo' although
all was working fine with Remis's initial example:
    Method arguments:
      #55 (Ljava/lang/Object;)V
      #57 REF_invokeStatic
RecordIntersectionTypeAndLambda.lambda$main$0:(LRecordIntersectionTypeAndLambda$Hello;Ljava/lang/Object;)V
      #60 (Ljava/lang/Record;)V

Referring to 'LambdaMetafactory::metafactory', we see that
'instantiatedMethodType' may be the same or a specialization of
'samMethodType' suggesting to use the SAM type too when the
instantiated type is compound like in the experimental fix below
(langtools:tier1 is OK on jdk14u):
    Method arguments:
      #57 (LRecLCE$Foo;)V
      #58 REF_invokeStatic RecLCE.lambda$main$0:(LRecLCE$Hello;LRecLCE$Foo;)V
      #57 (LRecLCE$Foo;)V

What do you think?
Bernard

diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java
@@ -438,6 +438,7 @@
         List<JCExpression> indy_args =
translate(syntheticInits.toList(), localContext.prev);

         //convert to an invokedynamic call
+        localContext.useSAM = tree.useSAM;
         result = makeMetafactoryIndyCall(context, sym.asHandle(), indy_args);
     }

@@ -888,6 +889,7 @@
         private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();

         private JCExpression receiverExpression = null;
+        private boolean useSAM = false;

         MemberReferenceToLambda(JCMemberReference tree,
ReferenceTranslationContext localContext, Symbol owner) {
             this.tree = tree;
@@ -911,6 +913,7 @@
                 slam.target = tree.target;
                 slam.type = tree.type;
                 slam.pos = tree.pos;
+                slam.useSAM = useSAM;
                 return slam;
             } finally {
                 make.at(prevPos);
@@ -954,6 +957,7 @@

             // Failsafe -- assure match-up
             boolean checkForIntersection = tree.varargsElement !=
null || implSize == descPTypes.size();
+            useSAM = checkForIntersection &&
localContext.interfaceParameterIsIntersectionOrUnionType();

             // Use parameter types of the implementation method
unless the unerased
             // SAM parameter type is an intersection type, in that case use the
@@ -963,18 +967,7 @@
             // are used as pointers to the current parameter type information
             // and are thus not usable afterwards.
             for (int i = 0; implPTypes.nonEmpty() && i < last; ++i) {
-                // By default use the implementation method parmeter type
-                Type parmType = implPTypes.head;
-                // If the unerased parameter type is a type variable whose
-                // bound is an intersection (eg. <T extends A & B>) then
-                // use the SAM parameter type
-                if (checkForIntersection && descPTypes.head.getKind()
== TypeKind.TYPEVAR) {
-                    TypeVar tv = (TypeVar) descPTypes.head;
-                    if (tv.getUpperBound().getKind() ==
TypeKind.INTERSECTION) {
-                        parmType = samPTypes.head;
-                    }
-                }
-                addParameter("x$" + i, parmType, true);
+                addParameter("x$" + i, useSAM ? samPTypes.head :
implPTypes.head, true);

                 // Advance to the next parameter
                 implPTypes = implPTypes.tail;
@@ -1094,7 +1087,7 @@
         List<LoadableConstant> staticArgs = List.of(
                 typeToMethodType(samSym.type),
                 refSym.asHandle(),
-                typeToMethodType(tree.getDescriptorType(types)));
+                typeToMethodType(context.useSAM ? samSym.type :
tree.getDescriptorType(types)));

         //computed indy arg types
         ListBuffer<Type> indy_args_types = new ListBuffer<>();
@@ -1826,6 +1819,8 @@
             /** list of methods to be bridged by the meta-factory */
             final List<Symbol> bridges;

+            boolean useSAM = false;
+
             TranslationContext(T tree) {
                 this.tree = tree;
                 this.owner = owner(true);
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java
@@ -1892,6 +1892,7 @@
         public JCTree body;
         public boolean canCompleteNormally = true;
         public ParameterKind paramKind;
+        public boolean useSAM = false;

         public JCLambda(List<JCVariableDecl> params,
                         JCTree body) {


More information about the compiler-dev mailing list