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

Type inference breakdown with generics in exhaustive switch

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      openjdk 24 2025-03-18
      OpenJDK Runtime Environment Temurin-24+36 (build 24+36)
      OpenJDK 64-Bit Server VM Temurin-24+36 (build 24+36, mixed mode, sharing)
      Distributor ID: Ubuntu
      Description: Ubuntu 24.04.2 LTS
      Release: 24.04
      Codename: noble

      A DESCRIPTION OF THE PROBLEM :
      I encountered what I consider to be a defect in the type inference (and unchecked casting) with pattern switches and sealed classes. What I had was something like:

      class Scratch {
        public static void main(String[] args) {
          var one = new TypeOne(1, null);
          var updated = withComments(one, "Hello, type inference.");
          System.out.println(updated);
        }
       
        public static <T extends SimpleSum> T withComments(T base, String comments) {
          return switch (base) {
            case TypeOne typeOne -> new TypeOne(typeOne.id(), comments);
            case TypeTwo typeTwo -> new TypeTwo(typeTwo.id(), comments);
          };
        }
       
        sealed interface SimpleSum permits TypeOne, TypeTwo {}
       
        record TypeOne(long id, String comments) implements SimpleSum {}
        record TypeTwo(long id, String comments) implements SimpleSum {}
      }

      Now this will simply fail to compile (OpenJDK Runtime Environment Temurin-24+36 (build 24+36)).

      error: incompatible types: bad type in switch expression
            case TypeOne typeOne -> new TypeOne(typeOne.id(), comments);
                                    ^
          TypeOne cannot be converted to T
        where T is a type-variable:
          T extends SimpleSum declared in method <T>withComments(T,String)

      Since the compiler fails to perform the inference that T is assignable to TypeOne in that branch by proof of the case. I was slightly disappointed but being used to the weakness in the type inference algorithm I simply ascribed the type:

        public static <T extends SimpleSum> T withComments(T base, String comments) {
          return switch (base) {
            case TypeOne typeOne -> (T) new TypeOne(typeOne.id(), comments);
            case TypeTwo typeTwo -> (T) new TypeTwo(typeTwo.id(), comments);
          };
        }

      this was met with this disappointing unchecked warning:

            case TypeOne typeOne -> (T) new TypeOne(typeOne.id(), comments);
                                        ^
        required: T
        found: TypeOne
        where T is a type-variable:
          T extends SimpleSum declared in method <T>withComments(T,String)

      So the final method sadly needs the lint rule:

        @SuppressWarnings("unchecked")
        public static <T extends SimpleSum> T withComments(T base, String comments) {
          return switch (base) {
            case TypeOne typeOne -> (T) new TypeOne(typeOne.id(), comments);
            case TypeTwo typeTwo -> (T) new TypeTwo(typeTwo.id(), comments);
          };
        }

      But that suppress warnings makes the function look dangerous when it isn't doing anything particularly wrong. The type check is there in the switch and ideally the inference algorithm could pick it up, but failing that I feel like the warning shouldn't fire since the switch branch is already a type check. Whatever T was to the caller, the record class captured in the branch arm of the switch will fulfill it.


            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: