[java] Add support for local records (Java 15 Preview)

This commit is contained in:
Andreas Dangel
2020-08-14 18:46:09 +02:00
parent 0a29732909
commit 4d2853ccc4
6 changed files with 73 additions and 9 deletions

View File

@ -3,6 +3,7 @@
* 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.
* Support Local Records with Java 15 Preview.
* Andreas Dangel 08/2020
*====================================================================
* Add support for record types introduced as a preview language
@ -468,11 +469,15 @@ public class JavaParser {
}
private void checkForRecordType() {
if (jdkVersion != 14 && jdkVersion != 15 || !preview) {
if (!isRecordTypeSupported()) {
throwParseException("Records are only supported with Java 14 Preview and Java 15 Preview");
}
}
private boolean isRecordTypeSupported() {
return (jdkVersion == 14 || jdkVersion == 15) && preview;
}
// This is a semantic LOOKAHEAD to determine if we're dealing with an assert
// Note that this can't be replaced with a syntactic lookahead
@ -1881,22 +1886,25 @@ void BlockStatement():
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
|
LOOKAHEAD(( "final" | Annotation() )* Type() <IDENTIFIER>)
LOOKAHEAD(2147483647, ( "final" | Annotation() )* Type() <IDENTIFIER>, {isRecordTypeSupported() && !isKeyword("record") || !isRecordTypeSupported()})
LocalVariableDeclaration() ";"
|
LOOKAHEAD({isRecordTypeSupported() && !isKeyword("record") || !isRecordTypeSupported()})
Statement()
|
// we don't need to lookahead further here
// the ambiguity between start of local class and local variable decl
// is already handled in the lookahead guarding LocalVariableDeclaration above.
LocalClassDecl()
// and start of local record and local variable decl or statement
// is already handled in the lookahead guarding LocalVariableDeclaration
// and Statement above.
LocalClassOrRecordDecl()
}
void LocalClassDecl() #void:
void LocalClassOrRecordDecl() #void:
{int mods = 0;}
{
// this preserves the modifiers of the local class.
// it allows for modifiers that are forbidden for local classes,
// this preserves the modifiers of the local class or record.
// it allows for modifiers that are forbidden for local classes and records,
// but anyway we are *not* checking modifiers for incompatibilities
// anywhere else in this grammar (and indeed the production Modifiers
// accepts any modifier explicitly for the purpose of forgiving modifier errors,
@ -1905,7 +1913,11 @@ void LocalClassDecl() #void:
// In particular, it unfortunately allows local class declarations to start
// with a "default" modifier, which introduces an ambiguity with default
// switch labels. This is guarded by a custom lookahead around SwitchLabel
mods=Modifiers() ClassOrInterfaceDeclaration(mods)
mods=Modifiers()
(
LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods)
| ClassOrInterfaceDeclaration(mods)
)
}
/*

View File

@ -63,6 +63,10 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
return true;
}
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}
/**
* @deprecated Renamed to {@link #getRecordComponents()}
*/

View File

@ -50,6 +50,7 @@ public class Java15PreviewTest {
ASTRecordDeclaration recordDecl = compilationUnit.getFirstDescendantOfType(ASTRecordDeclaration.class);
Assert.assertEquals("Point", recordDecl.getImage());
Assert.assertFalse(recordDecl.isNested());
Assert.assertFalse(recordDecl.isLocal());
Assert.assertTrue("Records are implicitly always final", recordDecl.isFinal());
List<ASTRecordComponent> components = recordDecl.getFirstChildOfType(ASTRecordComponentList.class)
.findChildrenOfType(ASTRecordComponent.class);
@ -101,9 +102,10 @@ public class Java15PreviewTest {
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.assertEquals(3, 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.getDeclarations().get(2) instanceof ASTClassOrInterfaceBodyDeclaration);
Assert.assertTrue(complex.getParent() instanceof ASTClassOrInterfaceBodyDeclaration);
ASTClassOrInterfaceBodyDeclaration complexParent = complex.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
Assert.assertEquals(DeclarationKind.RECORD, complexParent.getKind());
@ -153,4 +155,13 @@ public class Java15PreviewTest {
public void recordIsARestrictedIdentifier() {
java15p.parse("public class record {}");
}
@Test
public void localRecords() {
ASTCompilationUnit compilationUnit = java15p.parseResource("LocalRecords.java");
List<ASTRecordDeclaration> records = compilationUnit.findDescendantsOfType(ASTRecordDeclaration.class);
Assert.assertEquals(1, records.size());
Assert.assertEquals("MerchantSales", records.get(0).getSimpleName());
Assert.assertTrue(records.get(0).isLocal());
}
}

View File

@ -0,0 +1,26 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
import java.util.stream.Collectors;
import java.util.List;
/**
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
*/
public class LocalRecords {
public interface Merchant {}
public static double computeSales(Merchant merchant, int month) {
return month;
}
List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {
// Local record
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(Collectors.toList());
}
}

View File

@ -1,3 +1,7 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
/**
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
*/

View File

@ -1,3 +1,7 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
import java.io.IOException;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@ -21,7 +25,10 @@ public class Records {
this.real = real;
this.imaginary = imaginary;
}
public record Nested(int a) {}
public static class NestedClass { }
}