-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Description
child of #14890
from JEP:
If a record pattern names a generic record class but gives no type arguments (i.e., the record pattern uses a raw type) then the type arguments are always inferred. Inference works with nested record patterns. In fact it is possible to drop the type arguments in the outer record pattern as well, leading to the concise code
As of Java 21, type arguments for record patterns can be inferred by the compiler when not explicitly provided, JEP recommend to drop type arguments in record pattern as this leads to a more concise code.
Related reading from JLS : https://docs.oracle.com/javase/specs/jls/se22/html/jls-18.html#jls-18.5.5
Note: For compatibility reasons, type patterns do not support the implicit inference of type arguments
Config
$ cat config.xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="TreeWalker">
<module name="UnnecessaryTypeArgumentsWithRecordPattern"/>
</module>
</module>
Examples
public class Test {
interface IBox<T> {}
record Box<T>(T t) implements IBox<T> { }
void test1(Box<Box<String>> bbs) {
if (bbs instanceof Box<Box<String>>(Box<String>(var s))) { // 2 violations
System.out.println("String " + s);
}
if (bbs instanceof Box<Box<String>>(Box(var s))) { // violation
System.out.println("String " + s);
}
if (bbs instanceof Box(Box(var s))) {
System.out.println("String " + s);
}
// Here the compiler will infer that
// the entire instanceof pattern is Box<Box<String>>(Box<String>(var s)),
}
void test3(Box obj) { // raw use
// if (obj instanceof Box(var s)) { } this will not compile, compiler can't infer type
// if (obj instanceof Box<String>(var s)) { } this will not compile, Box' cannot be safely cast to 'Box<String>'
if (obj instanceof Box<?>(var s)) { // only wildcard is allowed, so we shouldn't violate type arguments if it is a wildcard
System.out.println("String " + s);
}
}
void test3(IBox<Box<String>> ibs) {
switch (ibs) {
case Box<Box<String>>(Box<String>(var s)) -> // 2 violations
System.out.println("String " + s);
default -> {}
}
switch (ibs) {
case Box(Box(var s)) -> // ok
System.out.println("String " + s);
default -> {}
}
}
}
Output
$ java -jar checkstyle-10.19.0-all.jar -c config.xml Test.java
Starting audit...
[ERROR] D:\Test.java:5:31: Unnecessary type arguments with record pattern. [UnnecessaryTypeArgumentsWithRecordPattern]
[ERROR] D:\Test.java:5:48: Unnecessary type arguments with record pattern. [UnnecessaryTypeArgumentsWithRecordPattern]
[ERROR] D:\Test.java:8:31: Unnecessary type arguments with record pattern.[UnnecessaryTypeArgumentsWithRecordPattern]
Audit done.
Checkstyle ends with 3 errors.
Implementation:
We will flag any usage of type arguments with record pattern except if it is a wildcard ?
. Because there are some cases ( raw type record) when the compiler can't infer and only ?
is required to make the record pattern compileable as shown in examples.
So to be clear we will always ignore ?
even if it will be a false negative and compiler can infer it.
Example :
void test(Object bbs) {
// if (bbs instanceof Box<Box<String>>(Box<String>(var s))) { } // Compile time error, 'Object' cannot be safely cast to 'Box<Box<String>>'
// if (bbs instanceof Box<Box<String>>(Box(var s))) { } // Compile time error, 'Object' cannot be safely cast to 'Box<Box<String>>'
if (bbs instanceof Box(Box(var s))) {
System.out.println(s);
}
// Here the compiler will infer that
// the entire instanceof pattern is Box<?>(Box<?>(var s)),
if (bbs instanceof Box<?>(Box<?>(var s))) { } // even if the compiler can infer the pattern without the wildcard but we will not flag this
}