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