Primitive type pattern (as actually specified) is considered harmful

Remi Forax forax at univ-mlv.fr
Wed Sep 10 09:39:25 UTC 2025


Hello all,
The idea of JEP 507 is for the following code:

  Object o = "foo";
  switch(o) {
    case String s -> ...
    default -> ...
  }
 
The "case String" recovers the dynamic class of the value.
The assignment does a widening and the pattern matching does the narrowing back to the original class.
This can be seen as the transformation chain, String -> Object -> String.

The JEP 507 proposes to apply the same principle to primitive types,
By example, the transformation byte -> int -> byte, the pattern matching acting as a kind of inverse operation.

For me, while this idea is coherent with itself, it fails at different levels.


# Primitive conversions can be lossy

With primitive types, the widening can be lossy (int to float is lossy, long to double is lossy),
and from a mathematical point of view inverting a lossy function makes no sense.

This give us this kind of puzzler:

  record Plane(float x, float y) {}

  void main() {
    Plane plane = new Plane(200_000_007, 16_777_219);

    switch (plane) {
      case Plane(int x, int y) -> IO.println("plane " + x +  " " + y);
      default -> IO.println("not a plane");
    }
  }

You may say that the bug lies in the fact that Java should not allow lossy conversions,
And I would agree, but it does not change the fact that conceptually, the pattern matching is trying to invert a lossy function, which again make no sense.


# This is the wrong semantics

For most people, int is equivalent to Integer!, this is also where we are aiming for Valhalla.
Given that a switch can match null but only using a separately case null, matching a primitive type or its corresponding wrapper types should be equivalent.

Sadly, this is not the semantics defined by the JEP 507.

I propose, instead of the semantics of the JEP 507, to use two rules:
- If the value switched upon is an Object, a "case int" should be equivalent to a "case Integer" and vice versa.

By example:

  Object o = ...
  switch(o) {
    case int i  -> ...
    default -> ...
  }

should be equivalent to

  Object o = ...
  switch(o) {
    case Integer i -> ...
    default -> ...
  }


- If the value switched upon is a primitive type, then only conversion that can occur is a boxing conversion.

  int v = ...
  switch(v) {
    case Integer _ -> ...  // ok
  }

If people want to know if an int can be safely converted to a byte, I think that using a static deconstructor method is better.

regards,
Rémi


More information about the amber-spec-experts mailing list