eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
164 stars 130 forks source link

[Switch][Sealed types] Incorrect exhaustiveness check leads to MatchException at runtime #3031

Closed srikanth-sankaran closed 1 month ago

srikanth-sankaran commented 1 month ago

The following program compiles fine with ECJ while it should not, but crashes at runtime with a MatchException:

abstract sealed class J<T1, T2> permits X.S, A {}

final class A extends J<Integer, String> {}

public class X<T> {

    final class S<U> extends J<T, U> {}

 int testExhaustive(J<Integer, String> ji) {
   return switch (ji) { // Exhaustive!
     case A a -> 42;
   //case X<Integer>.S<String> e -> 42;
   };
 }
 public static void main(String[] args) {
   X<Integer>.S<String> xs = null;
   System.out.println(new X<Integer>().testExhaustive(new X<Integer>().new S<String>()));
 }
}

When run:

Exception in thread "main" java.lang.MatchException
    at X.testExhaustive(X.java:10)
    at X.main(X.java:17)

javac OTOH rejects the program with: X.java:10: error: the switch expression does not cover all possible input values

srikanth-sankaran commented 1 month ago

But this program, compiles and runs fine with ECJ but javac fails to compile it (javac behavior is incorrect)

abstract sealed class J<T1, T2> permits X.S, A {}

final class A extends J<Integer, String> {}

public class X<T> {

    final class S<U> extends J<T, U> {}

 int testExhaustive(J<Integer, String> ji) {
   return switch (ji) { // Exhaustive!
     case A a -> 42;
  case X<Integer>.S<String> e -> 42;
   };
 }
 public static void main(String[] args) {
   X<Integer>.S<String> xs = null;
   System.out.println(new X<Integer>().testExhaustive(new X<Integer>().new S<String>()));
 }
}

javac complains: X.java:12: error: illegal start of type at the case X<Integer>.S<String> e -> 42; line. See that we are allowed to declare X<Integer>.S<String> xs = null; inside main

srikanth-sankaran commented 1 month ago

This program is rejected by javac too with X.java:26: error: illegal start of type but that looks like a bad parse of case pattern's type - see that inside main this statement O<Short>.M<Integer>.I<Long>.J<Integer, X> ji = (W<Short>.NG.I<Integer>.S<Long, Integer, X>) null; is accepted by javac without an error.

public class X {

    class O<T> {
        class M<U> {
            class I<K> {
                abstract sealed class J<V1, V2> permits W.NG.I.S {
                }
            }
        }
    }

    class W<T1> {
        class NG {
            class I<T2> {
                final class S<T3, T4, T5> extends O<T1>.M<T2>.I<T3>.J<T4, T5> {
                    S(O<T1>.M<T2>.I<T3> ei) {
                        ei.super();
                    }
                }
            }
        }
    }

    static int testExhaustive(O<Short>.M<Integer>.I<Long>.J<Integer, X> ji) {
        return switch (ji) { // Exhaustive!
        case W<Short>.NG.I<Integer>.S<Long, Integer, X> e -> 42;
        };
    }

    public static void main(String[] args) {
        System.out.println(X.testExhaustive(new X().new W<Short>().new NG().new I<Integer>().new S<Long, Integer, X>(
                new X().new O<Short>().new M<Integer>().new I<Long>())));
        O<Short>.M<Integer>.I<Long>.J<Integer, X> ji = (W<Short>.NG.I<Integer>.S<Long, Integer, X>) null;
    }
}
srikanth-sankaran commented 1 month ago

@stephan-herrmann - a second opinion that this is javac to failure to accept a valid program would be useful. TIA

stephan-herrmann commented 6 days ago

@stephan-herrmann - a second opinion that this is javac to failure to accept a valid program would be useful. TIA

I finally posted https://mail.openjdk.org/pipermail/compiler-dev/2024-November/028464.html