[java] Support Records with Java 15 Preview

This commit is contained in:
Andreas Dangel
2020-08-13 20:41:55 +02:00
parent e596a67297
commit e51519c5fd
5 changed files with 192 additions and 3 deletions

View File

@ -2,6 +2,7 @@
* Remove support for Java 13 preview language features.
* Promote text blocks as a permanent language features with Java 15.
* Support Pattern Matching for instanceof with Java 15 Preview.
* Support Records with Java 15 Preview.
* Andreas Dangel 08/2020
*====================================================================
* Add support for record types introduced as a preview language
@ -419,7 +420,7 @@ public class JavaParser {
throwParseException("With JDK 10, 'var' is a restricted local variable type and cannot be used for type declarations!");
}
if (jdkVersion >= 14 && preview && "record".equals(image)) {
throwParseException("With JDK 14 Preview, 'record' is a restricted identifier and cannot be used for type declarations!");
throwParseException("With JDK 14 Preview and JDK 15 Preview, 'record' is a restricted identifier and cannot be used for type declarations!");
}
}
private void checkForMultipleCaseLabels() {
@ -467,8 +468,8 @@ public class JavaParser {
}
private void checkForRecordType() {
if (jdkVersion != 14 || !preview) {
throwParseException("Records are only supported with Java 14 Preview");
if (jdkVersion != 14 && jdkVersion != 15 || !preview) {
throwParseException("Records are only supported with Java 14 Preview and Java 15 Preview");
}
}

View File

@ -57,6 +57,12 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
return isNested();
}
@Override
public boolean isFinal() {
// A record is implicitly final
return true;
}
/**
* @deprecated Renamed to {@link #getRecordComponents()}
*/

View File

@ -7,10 +7,12 @@ package net.sourceforge.pmd.lang.java.ast;
import java.util.List;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind;
public class Java15PreviewTest {
private final JavaParsingHelper java15p =
@ -42,4 +44,113 @@ public class Java15PreviewTest {
java15.parseResource("PatternMatchingInstanceof.java");
}
@Test
public void recordPoint() {
ASTCompilationUnit compilationUnit = java15p.parseResource("Point.java");
ASTRecordDeclaration recordDecl = compilationUnit.getFirstDescendantOfType(ASTRecordDeclaration.class);
Assert.assertEquals("Point", recordDecl.getImage());
Assert.assertFalse(recordDecl.isNested());
Assert.assertTrue("Records are implicitly always final", recordDecl.isFinal());
List<ASTRecordComponent> components = recordDecl.getFirstChildOfType(ASTRecordComponentList.class)
.findChildrenOfType(ASTRecordComponent.class);
Assert.assertEquals(2, components.size());
Assert.assertEquals("x", components.get(0).getVarId().getImage());
Assert.assertEquals("y", components.get(1).getVarId().getImage());
Assert.assertNull(components.get(0).getVarId().getNameDeclaration().getAccessNodeParent());
Assert.assertEquals(Integer.TYPE, components.get(0).getVarId().getNameDeclaration().getType());
Assert.assertEquals("int", components.get(0).getVarId().getNameDeclaration().getTypeImage());
}
@Test(expected = ParseException.class)
public void recordPointBeforeJava15PreviewShouldFail() {
java15.parseResource("Point.java");
}
@Test(expected = ParseException.class)
public void recordCtorWithThrowsShouldFail() {
java15p.parse(" record R {"
+ " R throws IOException {}"
+ " }");
}
@Test(expected = ParseException.class)
public void recordMustNotExtend() {
java15p.parse("record RecordEx(int x) extends Number { }");
}
@Test(expected = ParseException.class)
@Ignore("Should we check this?")
public void recordCannotBeAbstract() {
java15p.parse("abstract record RecordEx(int x) { }");
}
@Test(expected = ParseException.class)
@Ignore("Should we check this?")
public void recordCannotHaveInstanceFields() {
java15p.parse("record RecordFields(int x) { private int y = 1; }");
}
@Test
public void innerRecords() {
ASTCompilationUnit compilationUnit = java15p.parseResource("Records.java");
List<ASTRecordDeclaration> recordDecls = compilationUnit.findDescendantsOfType(ASTRecordDeclaration.class, true);
Assert.assertEquals(7, recordDecls.size());
ASTRecordDeclaration complex = recordDecls.get(0);
Assert.assertEquals("MyComplex", complex.getSimpleName());
Assert.assertTrue(complex.isNested());
Assert.assertEquals(0, getComponent(complex, 0).findChildrenOfType(ASTAnnotation.class).size());
Assert.assertEquals(1, getComponent(complex, 1).findChildrenOfType(ASTAnnotation.class).size());
Assert.assertEquals(2, complex.getDeclarations().size());
Assert.assertTrue(complex.getDeclarations().get(0).getChild(1) instanceof ASTConstructorDeclaration);
Assert.assertTrue(complex.getDeclarations().get(1).getChild(0) instanceof ASTRecordDeclaration);
Assert.assertTrue(complex.getParent() instanceof ASTClassOrInterfaceBodyDeclaration);
ASTClassOrInterfaceBodyDeclaration complexParent = complex.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
Assert.assertEquals(DeclarationKind.RECORD, complexParent.getKind());
Assert.assertSame(complex, complexParent.getDeclarationNode());
ASTRecordDeclaration nested = recordDecls.get(1);
Assert.assertEquals("Nested", nested.getSimpleName());
Assert.assertTrue(nested.isNested());
ASTRecordDeclaration range = recordDecls.get(2);
Assert.assertEquals("Range", range.getSimpleName());
Assert.assertEquals(2, range.getComponentList().size());
List<ASTRecordConstructorDeclaration> rangeConstructors = range.findDescendantsOfType(ASTRecordConstructorDeclaration.class);
Assert.assertEquals(1, rangeConstructors.size());
Assert.assertEquals("Range", rangeConstructors.get(0).getImage());
Assert.assertTrue(rangeConstructors.get(0).getChild(0) instanceof ASTAnnotation);
Assert.assertEquals(2, range.getDeclarations().size());
ASTRecordDeclaration varRec = recordDecls.get(3);
Assert.assertEquals("VarRec", varRec.getSimpleName());
Assert.assertEquals("x", getComponent(varRec, 0).getVarId().getImage());
Assert.assertTrue(getComponent(varRec, 0).isVarargs());
Assert.assertEquals(2, getComponent(varRec, 0).findChildrenOfType(ASTAnnotation.class).size());
Assert.assertEquals(1, getComponent(varRec, 0).getTypeNode().findDescendantsOfType(ASTAnnotation.class).size());
ASTRecordDeclaration arrayRec = recordDecls.get(4);
Assert.assertEquals("ArrayRec", arrayRec.getSimpleName());
Assert.assertEquals("x", getComponent(arrayRec, 0).getVarId().getImage());
Assert.assertTrue(getComponent(arrayRec, 0).getVarId().hasArrayType());
ASTRecordDeclaration emptyRec = recordDecls.get(5);
Assert.assertEquals("EmptyRec", emptyRec.getSimpleName());
Assert.assertEquals(0, emptyRec.getComponentList().size());
ASTRecordDeclaration personRec = recordDecls.get(6);
Assert.assertEquals("PersonRecord", personRec.getSimpleName());
ASTImplementsList impl = personRec.getFirstChildOfType(ASTImplementsList.class);
Assert.assertEquals(2, impl.findChildrenOfType(ASTClassOrInterfaceType.class).size());
}
private ASTRecordComponent getComponent(ASTRecordDeclaration arrayRec, int index) {
return (ASTRecordComponent) arrayRec.getComponentList().getChild(index);
}
@Test(expected = ParseException.class)
public void recordIsARestrictedIdentifier() {
java15p.parse("public class record {}");
}
}

View File

@ -0,0 +1,10 @@
/**
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
*/
public record Point(int x, int y) {
public static void main(String[] args) {
Point p = new Point(1, 2);
System.out.println("p = " + p);
}
}

View File

@ -0,0 +1,61 @@
import java.io.IOException;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
/**
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
*/
public class Records {
@Target(ElementType.TYPE_USE)
@interface Nullable { }
@Target({ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@interface MyAnnotation { }
public record MyComplex(int real, @Deprecated int imaginary) {
// explicit declaration of a canonical constructor
@MyAnnotation
public MyComplex(@MyAnnotation int real, int imaginary) {
if (real > 100) throw new IllegalArgumentException("too big");
this.real = real;
this.imaginary = imaginary;
}
public record Nested(int a) {}
}
public record Range(int lo, int hi) {
// compact record constructor
@MyAnnotation
public Range {
if (lo > hi) /* referring here to the implicit constructor parameters */
throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
}
public void foo() { }
}
public record VarRec(@Nullable @Deprecated String @Nullable ... x) {}
public record ArrayRec(int x[]) {}
public record EmptyRec<Type>() {
public void foo() { }
public Type bar() { return null; }
public static void baz() {
EmptyRec<String> r = new EmptyRec<>();
System.out.println(r);
}
}
// see https://www.javaspecialists.eu/archive/Issue276.html
public interface Person {
String firstName();
String lastName();
}
public record PersonRecord(String firstName, String lastName)
implements Person, java.io.Serializable {
}
}