-
Enhancement
-
Resolution: Unresolved
-
P4
-
None
-
None
-
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.
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.