Skip to content

Commit bb515cc

Browse files
TheElectronWillG1ng3r
authored andcommitted
Support records in JavaParsers
This is a port of scala/scala#9551
1 parent 92d3267 commit bb515cc

File tree

5 files changed

+109
-7
lines changed

5 files changed

+109
-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"
@@ -913,6 +914,10 @@ object StdNames {
913914
final val VOLATILEkw: N = kw("volatile")
914915
final val WHILEkw: N = kw("while")
915916

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

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

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

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import StdNames._
2020
import reporting._
2121
import dotty.tools.dotc.util.SourceFile
2222
import util.Spans._
23+
2324
import scala.collection.mutable.ListBuffer
25+
import scala.collection.immutable.ListMap
2426

2527
object JavaParsers {
2628

@@ -96,8 +98,12 @@ object JavaParsers {
9698
def javaLangDot(name: Name): Tree =
9799
Select(javaDot(nme.lang), name)
98100

101+
/** Tree representing `java.lang.Object` */
99102
def javaLangObject(): Tree = javaLangDot(tpnme.Object)
100103

104+
/** Tree representing `java.lang.Record` */
105+
def javaLangRecord(): Tree = javaLangDot(tpnme.Record)
106+
101107
def arrayOf(tpt: Tree): AppliedTypeTree =
102108
AppliedTypeTree(scalaDot(tpnme.Array), List(tpt))
103109

@@ -555,6 +561,14 @@ object JavaParsers {
555561

556562
def definesInterface(token: Int): Boolean = token == INTERFACE || token == AT
557563

564+
/** If the next token is the identifier "record", convert it into the RECORD token.
565+
* This makes it easier to handle records in various parts of the code,
566+
* in particular when a `parentToken` is passed to some functions.
567+
*/
568+
def adaptRecordIdentifier(): Unit =
569+
if in.token == IDENTIFIER && in.name == jnme.RECORDid then
570+
in.token = RECORD
571+
558572
def termDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = {
559573
val inInterface = definesInterface(parentToken)
560574
val tparams = if (in.token == LT) typeParams(Flags.JavaDefined | Flags.Param) else List()
@@ -581,6 +595,16 @@ object JavaParsers {
581595
TypeTree(), methodBody()).withMods(mods)
582596
}
583597
}
598+
} else if (in.token == LBRACE && rtptName != nme.EMPTY && parentToken == RECORD) {
599+
/*
600+
record RecordName(T param1, ...) {
601+
RecordName { // <- here
602+
// methodBody
603+
}
604+
}
605+
*/
606+
methodBody()
607+
Nil
584608
}
585609
else {
586610
var mods1 = mods
@@ -717,12 +741,11 @@ object JavaParsers {
717741
ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1)
718742
}
719743

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)
744+
def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match
745+
case CLASS | ENUM | RECORD | INTERFACE | AT =>
746+
typeDecl(start, if definesInterface(parentToken) then mods | Flags.JavaStatic else mods)
723747
case _ =>
724748
termDecl(start, mods, parentToken, parentTParams)
725-
}
726749

727750
def makeCompanionObject(cdef: TypeDef, statics: List[Tree]): Tree =
728751
atSpan(cdef.span) {
@@ -804,6 +827,49 @@ object JavaParsers {
804827
addCompanionObject(statics, cls)
805828
}
806829

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

950-
def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match {
1017+
def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match
9511018
case ENUM => enumDecl(start, mods)
9521019
case INTERFACE => interfaceDecl(start, mods)
9531020
case AT => annotationDecl(start, mods)
9541021
case CLASS => classDecl(start, mods)
1022+
case RECORD => recordDecl(start, mods)
9551023
case _ => in.nextToken(); syntaxError(em"illegal start of type declaration", skipIt = true); List(errorTypeTree)
956-
}
9571024

9581025
def tryConstant: Option[Constant] = {
9591026
val negate = in.token match {
@@ -1004,6 +1071,7 @@ object JavaParsers {
10041071
if (in.token != EOF) {
10051072
val start = in.offset
10061073
val mods = modifiers(inInterface = false)
1074+
adaptRecordIdentifier() // needed for typeDecl
10071075
buf ++= typeDecl(start, mods)
10081076
}
10091077
}

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)