Bytecode of when syntax for enum type

The when inside function extension equals to if-else syntax, and it works well before API 30 because other newer enum type at below does not exsits so it must be unreachable.
The second function extension2 works well on API 30 or later, but it throws NoSuchFieldError before API 30.

@SuppressLint("NewApi")
fun Bitmap.CompressFormat.extension(): String {
    return when {
        this === Bitmap.CompressFormat.JPEG -> ".jpeg"
        this === Bitmap.CompressFormat.PNG -> ".png"
        this === Bitmap.CompressFormat.WEBP -> ".webp"
        // API 30
        this === Bitmap.CompressFormat.WEBP_LOSSY -> ".webp"
        // API 30
        this === Bitmap.CompressFormat.WEBP_LOSSLESS -> ".webp"
        else -> throw AssertionError("unreachable code")
    }
}

@SuppressLint("NewApi")
fun Bitmap.CompressFormat.extension2(): String {
    return when (this) {
        Bitmap.CompressFormat.JPEG -> ".jpeg"
        Bitmap.CompressFormat.PNG -> ".png"
        Bitmap.CompressFormat.WEBP -> ".webp"
        // API 30
        Bitmap.CompressFormat.WEBP_LOSSY -> ".webp"
        // API 30
        Bitmap.CompressFormat.WEBP_LOSSLESS -> ".webp"
    }
}

I’ve looked at the bytecode of extension2. There is a synthetic class called $WhenMappings that calls all the enum types, and I think the hardcode way makes no sense. I mean it’s possible to make WhenMappings not to hardcode the enums?

public final class XXX$WhenMappings {
   // $FF: synthetic field
   public static final int[] $EnumSwitchMapping$0 = new int[CompressFormat.values().length];

   //.......

   static {
      //.....
      $EnumSwitchMapping$0[CompressFormat.JPEG.ordinal()] = 1;
      $EnumSwitchMapping$0[CompressFormat.PNG.ordinal()] = 2;
      $EnumSwitchMapping$0[CompressFormat.WEBP.ordinal()] = 3;
      $EnumSwitchMapping$0[CompressFormat.WEBP_LOSSY.ordinal()] = 4;
      $EnumSwitchMapping$0[CompressFormat.WEBP_LOSSLESS.ordinal()] = 5;
   }
}
/*
bytecode
   L2
    TABLESWITCH
      1: L3
      2: L4
      3: L5
      4: L6
      5: L7
      default: L8
*/

So you want to run the code that references e.g. Bitmap.CompressFormat.WEBP_LOSSY using framework libraries that don’t know about this field? I’m not very familiar with Android, but it sounds like a bad idea from the beginning. I’m actually surprised that extension() worked fine for you. Did you try to run it on something else that jpeg, png or webp? Because maybe it didn’t crash only because it never reached the WEBP_LOSSY check. Ok, I guess it makes sense to assume that below API 30 it can’t be anything else than jpg, png or webp. Still, this is pretty strange and rare that we reference non-existing code, so it is no surprise that Kotlin compiler generates unsafe code for you.

I guess WhenMappings is just an optimization. Using if-else is linear to the number of items in the enum. WhenMappings is constant.

2 Likes