[java] Add support for local records (Java 15 Preview)
This commit is contained in:
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -63,6 +63,10 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return getParent() instanceof ASTBlockStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to {@link #getRecordComponents()}
|
||||
*/
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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>
|
||||
*/
|
||||
|
@ -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 { }
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user