Skip to content

Commit cb56936

Browse files
Support records in JavaParsers
This is a port of scala/scala#9551
1 parent ce65296 commit cb56936

File tree

5 files changed

+106
-7
lines changed

5 files changed

+106
-7
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ object StdNames {
204204
final val Null: N = "Null"
205205
final val Object: N = "Object"
206206
final val FromJavaObject: N = "<FromJavaObject>"
207+
final val Record: N = "Record"
207208
final val Product: N = "Product"
208209
final val PartialFunction: N = "PartialFunction"
209210
final val PrefixType: N = "PrefixType"
@@ -912,6 +913,10 @@ object StdNames {
912913
final val VOLATILEkw: N = kw("volatile")
913914
final val WHILEkw: N = kw("while")
914915

916+
final val RECORDid: N = "record"
917+
final val VARid: N = "var"
918+
final val YIELDid: N = "yield"
919+
915920
final val BoxedBoolean: N = "java.lang.Boolean"
916921
final val BoxedByte: N = "java.lang.Byte"
917922
final val BoxedCharacter: N = "java.lang.Character"
@@ -944,6 +949,8 @@ object StdNames {
944949
final val JavaSerializable: N = "java.io.Serializable"
945950
}
946951

952+
953+
947954
class JavaTermNames extends JavaNames[TermName] {
948955
protected def fromString(s: String): TermName = termName(s)
949956
}

compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,12 @@ object JavaParsers {
9696
def javaLangDot(name: Name): Tree =
9797
Select(javaDot(nme.lang), name)
9898

99+
/** Tree representing `java.lang.Object` */
99100
def javaLangObject(): Tree = javaLangDot(tpnme.Object)
100101

102+
/** Tree representing `java.lang.Record` */
103+
def javaLangRecord(): Tree = javaLangDot(tpnme.Record)
104+
101105
def arrayOf(tpt: Tree): AppliedTypeTree =
102106
AppliedTypeTree(scalaDot(tpnme.Array), List(tpt))
103107

@@ -555,6 +559,14 @@ object JavaParsers {
555559

556560
def definesInterface(token: Int): Boolean = token == INTERFACE || token == AT
557561

562+
/** If the next token is the identifier "record", convert it into the RECORD token.
563+
* This makes it easier to handle records in various parts of the code,
564+
* in particular when a `parentToken` is passed to some functions.
565+
*/
566+
def adaptRecordIdentifier(): Unit =
567+
if in.token == IDENTIFIER && in.name == jnme.RECORDid then
568+
in.token = RECORD
569+
558570
def termDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = {
559571
val inInterface = definesInterface(parentToken)
560572
val tparams = if (in.token == LT) typeParams(Flags.JavaDefined | Flags.Param) else List()
@@ -581,6 +593,16 @@ object JavaParsers {
581593
TypeTree(), methodBody()).withMods(mods)
582594
}
583595
}
596+
} else if (in.token == LBRACE && rtptName != nme.EMPTY && parentToken == RECORD) {
597+
/*
598+
record RecordName(T param1, ...) {
599+
RecordName { // <- here
600+
// methodBody
601+
}
602+
}
603+
*/
604+
methodBody()
605+
Nil
584606
}
585607
else {
586608
var mods1 = mods
@@ -717,12 +739,11 @@ object JavaParsers {
717739
ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1)
718740
}
719741

720-
def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match {
721-
case CLASS | ENUM | INTERFACE | AT =>
722-
typeDecl(start, if (definesInterface(parentToken)) mods | Flags.JavaStatic else mods)
742+
def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match
743+
case CLASS | ENUM | RECORD | INTERFACE | AT =>
744+
typeDecl(start, if definesInterface(parentToken) then mods | Flags.JavaStatic else mods)
723745
case _ =>
724746
termDecl(start, mods, parentToken, parentTParams)
725-
}
726747

727748
def makeCompanionObject(cdef: TypeDef, statics: List[Tree]): Tree =
728749
atSpan(cdef.span) {
@@ -804,6 +825,48 @@ object JavaParsers {
804825
addCompanionObject(statics, cls)
805826
}
806827

828+
def recordDecl(start: Offset, mods: Modifiers): List[Tree] =
829+
accept(RECORD)
830+
val nameOffset = in.offset
831+
val name = identForType()
832+
val tparams = typeParams()
833+
val header = formalParams()
834+
val superclass = javaLangRecord() // records always extend java.lang.Record
835+
val interfaces = interfacesOpt() // records may implement interfaces
836+
val (statics, body) = typeBody(RECORD, name, tparams)
837+
838+
// We need to generate accessors for every param, if no method with the same name is already defined
839+
840+
var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).toMap
841+
842+
for case DefDef(name, paramss, tpt, rhs) <- body
843+
if paramss.isEmpty && fieldsByName.contains(name)
844+
do
845+
fieldsByName -= name
846+
end for
847+
848+
val accessors =
849+
(for (name, (tpt, annots)) <- fieldsByName yield
850+
DefDef(name, Nil, tpt, unimplementedExpr).withMods(Modifiers(Flags.JavaDefined | Flags.Method | Flags.Synthetic))
851+
).toList
852+
853+
// generate the canonical constructor
854+
val canonicalConstructor = makeConstructor(header, tparams)
855+
856+
// return the trees, probably with addCompanionObject (like classDecl)
857+
val recordTypeDef = atSpan(start, nameOffset) {
858+
TypeDef(name,
859+
makeTemplate(
860+
parents = superclass :: interfaces,
861+
stats = canonicalConstructor :: accessors ::: body,
862+
tparams = tparams,
863+
false
864+
)
865+
)
866+
}
867+
addCompanionObject(statics, recordTypeDef)
868+
end recordDecl
869+
807870
def interfaceDecl(start: Offset, mods: Modifiers): List[Tree] = {
808871
accept(INTERFACE)
809872
val nameOffset = in.offset
@@ -846,7 +909,8 @@ object JavaParsers {
846909
else if (in.token == SEMI)
847910
in.nextToken()
848911
else {
849-
if (in.token == ENUM || definesInterface(in.token)) mods |= Flags.JavaStatic
912+
adaptRecordIdentifier()
913+
if (in.token == ENUM || in.token == RECORD || definesInterface(in.token)) mods |= Flags.JavaStatic
850914
val decls = memberDecl(start, mods, parentToken, parentTParams)
851915
(if (mods.is(Flags.JavaStatic) || inInterface && !(decls exists (_.isInstanceOf[DefDef])))
852916
statics
@@ -947,13 +1011,13 @@ object JavaParsers {
9471011
}
9481012
}
9491013

950-
def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match {
1014+
def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match
9511015
case ENUM => enumDecl(start, mods)
9521016
case INTERFACE => interfaceDecl(start, mods)
9531017
case AT => annotationDecl(start, mods)
9541018
case CLASS => classDecl(start, mods)
1019+
case RECORD => recordDecl(start, mods)
9551020
case _ => in.nextToken(); syntaxError(em"illegal start of type declaration", skipIt = true); List(errorTypeTree)
956-
}
9571021

9581022
def tryConstant: Option[Constant] = {
9591023
val negate = in.token match {
@@ -1004,6 +1068,7 @@ object JavaParsers {
10041068
if (in.token != EOF) {
10051069
val start = in.offset
10061070
val mods = modifiers(inInterface = false)
1071+
adaptRecordIdentifier() // needed for typeDecl
10071072
buf ++= typeDecl(start, mods)
10081073
}
10091074
}

compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ object JavaTokens extends TokensCommon {
4141
inline val SWITCH = 133; enter(SWITCH, "switch")
4242
inline val ASSERT = 134; enter(ASSERT, "assert")
4343

44+
/** contextual keywords (turned into keywords in certain conditions, see JLS 3.9 of Java 9+) */
45+
inline val RECORD = 135; enter(RECORD, "record")
46+
4447
/** special symbols */
4548
inline val EQEQ = 140
4649
inline val BANGEQ = 141
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
object C:
2+
def useR1 =
3+
// constructor signature
4+
val r = R1(123, "hello")
5+
6+
// accessors
7+
val i: Int = r.i
8+
val s: String = r.s
9+
10+
// methods
11+
val iRes: Int = r.getInt()
12+
val sRes: String = r.getString()
13+
14+
// supertype
15+
val record: java.lang.Record = r

tests/pos/java-records/R1.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
public record R1(int i, String s) {
2+
public String getString() {
3+
return s + i;
4+
}
5+
6+
public int getInt() {
7+
return 0;
8+
}
9+
}

0 commit comments

Comments
 (0)