Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8176577

Anonymous class instance creation expression with diamond compatibility constraint is reduced to anonymous class type

    Details

      Description

      let's consider following code:

      class MyType<T> {}

      class MyList<T> {
          MyList<T> copyThis() { return null; }
      }

      class Foo<T> {
          Foo(MyType<String> a){ }
      }

      public class Test26 {
          public static void main(String argv[]) {
              MyList<Foo> l1 = new MyList<>();
              m2(l1, m1(
                         new Foo<>(new MyType()){ }
                       ).copyThis());
          }
          public static <T> MyList<T> m2(MyList<T> l1, MyList<T> l2) { return null; }
          public static <U> MyList<U> m1(U item) { return null; }
      }

      it fails to compile by javac from JDK 9 build 160 with following error:

      Error:(15, 11) java: incompatible types: inference variable T has incompatible equality constraints <anonymous Foo>,Foo

      But it seems that this code should compile successfully because:

      1. When performing invocation type inference for m1 invocation, a temporary method is chosen according to following assertion from 18.2.1:

      If the expression is a class instance creation expression or a method invocation expression, the constraint reduces to the bound set B3 which would be used to determine the expression's invocation type when targeting T, as defined in §18.5.2. (For a class instance creation expression, the corresponding "method" used for inference is defined in §15.9.3).

      2. The temporary method return type is an anonymous class superclass as it's specified by following assertions from 15.9.3 JLS 9 draft:

      ... If C is an anonymous class, let D be the superclass or superinterface of C named by the class instance creation expression.

      The return type of mj is θj applied to D<F1,...,Fp>.

      3. So the return type of the temporary method is Foo<F1>.

      4. Unchecked conversion is necessary in order for constructor Foo to be applicable so the return type D is erased and should be Foo.

      5. Thus m1 inference variable U should be inferred as Foo and m1 return type should be inferred as MyList<Foo>.

      6. Finally m2 inference variable T should be inferred as Foo with no incompatible constraints.

        Issue Links

          Activity

          Hide
          sadayapalam Srikanth Adayapalam added a comment -
          We discussed this in the javac team and the emerging consensus is that this is really a corner case territory - Since this realistically affects only anon diamond in unchecked method call context, it should be ok to defer. It is also very late in the game for making any risky changes - given it is a more than a decade since the gentle and not so gentle nudge away from raw types has been in force, this does not look to me the kind of issue that needs addressal at this point in the release cycle.
          Show
          sadayapalam Srikanth Adayapalam added a comment - We discussed this in the javac team and the emerging consensus is that this is really a corner case territory - Since this realistically affects only anon diamond in unchecked method call context, it should be ok to defer. It is also very late in the game for making any risky changes - given it is a more than a decade since the gentle and not so gentle nudge away from raw types has been in force, this does not look to me the kind of issue that needs addressal at this point in the release cycle.
          Hide
          mtrudeau Michel Trudeau added a comment - - edited
          9-defer-request justification:
          This is a corner case, it only affects anonymous diamond in unchecked method call context. It is low priority and risky to fix at this point in the release cycle.
          - This is an ILW P3 bug.
          Show
          mtrudeau Michel Trudeau added a comment - - edited 9-defer-request justification: This is a corner case, it only affects anonymous diamond in unchecked method call context. It is low priority and risky to fix at this point in the release cycle. - This is an ILW P3 bug.
          Hide
          sadayapalam Srikanth Adayapalam added a comment -
          Reduced test case:

          public class X<T> {
          X<T> copyThis() { return null; }
          X(X<String> a) {}
          X() {}

              public static void main(String argv[]) {
                  
                  m2((X<X>) null, m1(
                             new X<>(new X()){ }
                           ).copyThis());
              }

              public static <T> X<T> m2(X<T> l1, X<T> l2) { return null; }
              public static <U> X<U> m1(U item) { return null; }
          }

          This test case (and the original) compiles fine with eclipse oxygen 1.a
          Show
          sadayapalam Srikanth Adayapalam added a comment - Reduced test case: public class X<T> { X<T> copyThis() { return null; } X(X<String> a) {} X() {}     public static void main(String argv[]) {                  m2((X<X>) null, m1(                    new X<>(new X()){ }                  ).copyThis());     }     public static <T> X<T> m2(X<T> l1, X<T> l2) { return null; }     public static <U> X<U> m1(U item) { return null; } } This test case (and the original) compiles fine with eclipse oxygen 1.a
          Hide
          sadayapalam Srikanth Adayapalam added a comment - - edited
          After investigation, I have arrived at the conclusion that there is no defect here and the compiler behavior is according to the specification:
          I quote here under the relevant portions (as applicable to the test case) of the specification to trace the chain of reasoning:

          (1) 15.9.1 Determining the Class being Instantiated:

          ...

          If the class instance creation expression ends in a class body, then the class being instantiated is an anonymous class. Then

          • If the class instance creation expression is unqualified, then:

          ...

          If the Identifier in ClassOrInterfaceTypeToInstantiate denotes a class, C , then an anonymous direct subclass of C is declared. If TypeArguments is present, then C
          has type arguments given by TypeArguments; if <> is present, then C will have its type arguments inferred in §15.9.3;

          15.9.2 Determining Enclosing Instances:

          Not applicable for this case, since the instantiated class is not an inner class.

          15.9.3 Choosing the Constructor and its Arguments:

          Let C be the class being instantiated. To create an instance of C , i , a constructor of
          C is chosen at compile time by the following rules.
          First, the actual arguments to the constructor invocation are determined:
          • If C is an anonymous class with direct superclass S , then:
          – If S is not an inner class, or if S is a local class that occurs in a static context,
          then the arguments to the constructor are the arguments in the argument list
          of the class instance creation expression, if any, in the order they appear in
          the expression.

          ...

          Second, a constructor of C and corresponding throws clause and return type are
          determined:

          ...

          • If the class instance creation expression uses <> , then:

          ...

          If C is an anonymous class,
          let D be the superclass or superinterface of C named by the class instance creation
          expression.
          If D is a class, let c 1 ... c n be the constructors of class D . If D is an interface, let
          c 1 ... c n be a singleton list (n = 1) containing the zero-argument constructor of
          class Object .
          A list of methods m 1 ... m n is defined for the purpose of overload resolution and type
          argument inference. For all j (1 ≤ j ≤ n), m j is defined in terms of c j as follows:

          ...

          To choose a constructor, we temporarily consider m 1 ... m n to be members of D .
          One of m 1 ... m n is chosen, as determined by the class instance creation expression's
          argument expressions, using the process specified in §15.12.2.
          If there is no unique most specific method that is both applicable and accessible,
          then a compile-time error occurs.
          Otherwise, where m j is the chosen method:

          ...

          If C is an anonymous class, then C 's anonymous constructor is chosen as the
          constructor of C . Its body consists of an explicit constructor invocation of c j .
          The throws clause of the chosen constructor includes the exceptions in the
          throws clause determined for m j .
          *The return type corresponding to the chosen constructor is the anonymous
          class type.*

          ...

          *The type of the class instance creation expression is the return type corresponding
          to the chosen constructor, as defined above.*

          The most crucial passages are:

          (1) The type of the class instance creation expression is the return type corresponding
          to the chosen constructor, as defined above.

          AND

          (2) The return type corresponding to the chosen constructor is the anonymous
          class type.

          Thus the type of the expression

          new Foo<>(new MyType()){ }

          is really the "anonymous subclass of Foo"

          Now when that is passed to

          public static <U> MyList<U> m1(U item) { return null; }

          m1 returns MyList<anonymous subclass of Foo>

          and the chained method call to copyThis will simply return the receiver type which is simply

          MyList<anonymous subclass of Foo>

          So now with respect to the call for m2, its arguments have the types MyList<Foo> and MyList<anonymous subclass of Foo> both constrained to be equal to the same type variable T of m2 - resulting in a valid complaint from the compiler.

          A crucial point to note here is that the invocation of m1 is NOT a poly expression since it is not
          occurring in either in an assignment context or invocation context and is as such not influenced by any target type in invocation type inference.

          (The chained call to copyThis occurs in an invocation context, but is also not a poly expression since it is not a generic call)
          Show
          sadayapalam Srikanth Adayapalam added a comment - - edited After investigation, I have arrived at the conclusion that there is no defect here and the compiler behavior is according to the specification: I quote here under the relevant portions (as applicable to the test case) of the specification to trace the chain of reasoning: (1) 15.9.1 Determining the Class being Instantiated: ... If the class instance creation expression ends in a class body, then the class being instantiated is an anonymous class. Then • If the class instance creation expression is unqualified, then: ... If the Identifier in ClassOrInterfaceTypeToInstantiate denotes a class, C , then an anonymous direct subclass of C is declared. If TypeArguments is present, then C has type arguments given by TypeArguments; if <> is present, then C will have its type arguments inferred in §15.9.3; 15.9.2 Determining Enclosing Instances: Not applicable for this case, since the instantiated class is not an inner class. 15.9.3 Choosing the Constructor and its Arguments: Let C be the class being instantiated. To create an instance of C , i , a constructor of C is chosen at compile time by the following rules. First, the actual arguments to the constructor invocation are determined: • If C is an anonymous class with direct superclass S , then: – If S is not an inner class, or if S is a local class that occurs in a static context, then the arguments to the constructor are the arguments in the argument list of the class instance creation expression, if any, in the order they appear in the expression. ... Second, a constructor of C and corresponding throws clause and return type are determined: ... • If the class instance creation expression uses <> , then: ... If C is an anonymous class, let D be the superclass or superinterface of C named by the class instance creation expression. If D is a class, let c 1 ... c n be the constructors of class D . If D is an interface, let c 1 ... c n be a singleton list (n = 1) containing the zero-argument constructor of class Object . A list of methods m 1 ... m n is defined for the purpose of overload resolution and type argument inference. For all j (1 ≤ j ≤ n), m j is defined in terms of c j as follows: ... To choose a constructor, we temporarily consider m 1 ... m n to be members of D . One of m 1 ... m n is chosen, as determined by the class instance creation expression's argument expressions, using the process specified in §15.12.2. If there is no unique most specific method that is both applicable and accessible, then a compile-time error occurs. Otherwise, where m j is the chosen method: ... If C is an anonymous class, then C 's anonymous constructor is chosen as the constructor of C . Its body consists of an explicit constructor invocation of c j . The throws clause of the chosen constructor includes the exceptions in the throws clause determined for m j . *The return type corresponding to the chosen constructor is the anonymous class type.* ... *The type of the class instance creation expression is the return type corresponding to the chosen constructor, as defined above.* The most crucial passages are: (1) The type of the class instance creation expression is the return type corresponding to the chosen constructor, as defined above. AND (2) The return type corresponding to the chosen constructor is the anonymous class type. Thus the type of the expression new Foo<>(new MyType()){ } is really the "anonymous subclass of Foo" Now when that is passed to public static <U> MyList<U> m1(U item) { return null; } m1 returns MyList<anonymous subclass of Foo> and the chained method call to copyThis will simply return the receiver type which is simply MyList<anonymous subclass of Foo> So now with respect to the call for m2, its arguments have the types MyList<Foo> and MyList<anonymous subclass of Foo> both constrained to be equal to the same type variable T of m2 - resulting in a valid complaint from the compiler. A crucial point to note here is that the invocation of m1 is NOT a poly expression since it is not occurring in either in an assignment context or invocation context and is as such not influenced by any target type in invocation type inference. (The chained call to copyThis occurs in an invocation context, but is also not a poly expression since it is not a generic call)
          Hide
          sadayapalam Srikanth Adayapalam added a comment -
          See that this slightly modified program compiles fine as it should:

          class MyType<T> {}

          class MyList<T> {
              MyList<T> copyThis() { return null; }
          }

          class Foo<T> {
              Foo(MyType<String> a){ }
          }

          public class X {
              public static void main(String argv[]) {
                  MyList<Foo> l1 = new MyList<>();
                  m2(l1, m1(
                             new Foo<>(new MyType()){ }
                           ));
              }
              public static <T> MyList<T> m2(MyList<T> l1, MyList<T> l2) { return null; }
              public static <U> MyList<U> m1(U item) { return null; }
          }

          The difference is that in this case the call to m1 is a poly expression occuring in an invocation context and so now is susceptible to
          influence of imposition of target type from the outer inference context.
          Show
          sadayapalam Srikanth Adayapalam added a comment - See that this slightly modified program compiles fine as it should: class MyType<T> {} class MyList<T> {     MyList<T> copyThis() { return null; } } class Foo<T> {     Foo(MyType<String> a){ } } public class X {     public static void main(String argv[]) {         MyList<Foo> l1 = new MyList<>();         m2(l1, m1(                    new Foo<>(new MyType()){ }                  ));     }     public static <T> MyList<T> m2(MyList<T> l1, MyList<T> l2) { return null; }     public static <U> MyList<U> m1(U item) { return null; } } The difference is that in this case the call to m1 is a poly expression occuring in an invocation context and so now is susceptible to influence of imposition of target type from the outer inference context.

            People

            • Assignee:
              sadayapalam Srikanth Adayapalam
              Reporter:
              grakov Georgiy Rakov (Inactive)
            • Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: