Skip to content

Commit

Permalink
Support records in JavaParsers
Browse files Browse the repository at this point in the history
This is a port of scala/scala#9551
  • Loading branch information
TheElectronWill committed Mar 7, 2023
1 parent ce65296 commit cb56936
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 7 deletions.
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ object StdNames {
final val Null: N = "Null"
final val Object: N = "Object"
final val FromJavaObject: N = "<FromJavaObject>"
final val Record: N = "Record"
final val Product: N = "Product"
final val PartialFunction: N = "PartialFunction"
final val PrefixType: N = "PrefixType"
Expand Down Expand Up @@ -912,6 +913,10 @@ object StdNames {
final val VOLATILEkw: N = kw("volatile")
final val WHILEkw: N = kw("while")

final val RECORDid: N = "record"
final val VARid: N = "var"
final val YIELDid: N = "yield"

final val BoxedBoolean: N = "java.lang.Boolean"
final val BoxedByte: N = "java.lang.Byte"
final val BoxedCharacter: N = "java.lang.Character"
Expand Down Expand Up @@ -944,6 +949,8 @@ object StdNames {
final val JavaSerializable: N = "java.io.Serializable"
}



class JavaTermNames extends JavaNames[TermName] {
protected def fromString(s: String): TermName = termName(s)
}
Expand Down
79 changes: 72 additions & 7 deletions compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,12 @@ object JavaParsers {
def javaLangDot(name: Name): Tree =
Select(javaDot(nme.lang), name)

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

/** Tree representing `java.lang.Record` */
def javaLangRecord(): Tree = javaLangDot(tpnme.Record)

def arrayOf(tpt: Tree): AppliedTypeTree =
AppliedTypeTree(scalaDot(tpnme.Array), List(tpt))

Expand Down Expand Up @@ -555,6 +559,14 @@ object JavaParsers {

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

/** If the next token is the identifier "record", convert it into the RECORD token.
* This makes it easier to handle records in various parts of the code,
* in particular when a `parentToken` is passed to some functions.
*/
def adaptRecordIdentifier(): Unit =
if in.token == IDENTIFIER && in.name == jnme.RECORDid then
in.token = RECORD

def termDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = {
val inInterface = definesInterface(parentToken)
val tparams = if (in.token == LT) typeParams(Flags.JavaDefined | Flags.Param) else List()
Expand All @@ -581,6 +593,16 @@ object JavaParsers {
TypeTree(), methodBody()).withMods(mods)
}
}
} else if (in.token == LBRACE && rtptName != nme.EMPTY && parentToken == RECORD) {
/*
record RecordName(T param1, ...) {
RecordName { // <- here
// methodBody
}
}
*/
methodBody()
Nil
}
else {
var mods1 = mods
Expand Down Expand Up @@ -717,12 +739,11 @@ object JavaParsers {
ValDef(name, tpt2, if (mods.is(Flags.Param)) EmptyTree else unimplementedExpr).withMods(mods1)
}

def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match {
case CLASS | ENUM | INTERFACE | AT =>
typeDecl(start, if (definesInterface(parentToken)) mods | Flags.JavaStatic else mods)
def memberDecl(start: Offset, mods: Modifiers, parentToken: Int, parentTParams: List[TypeDef]): List[Tree] = in.token match
case CLASS | ENUM | RECORD | INTERFACE | AT =>
typeDecl(start, if definesInterface(parentToken) then mods | Flags.JavaStatic else mods)
case _ =>
termDecl(start, mods, parentToken, parentTParams)
}

def makeCompanionObject(cdef: TypeDef, statics: List[Tree]): Tree =
atSpan(cdef.span) {
Expand Down Expand Up @@ -804,6 +825,48 @@ object JavaParsers {
addCompanionObject(statics, cls)
}

def recordDecl(start: Offset, mods: Modifiers): List[Tree] =
accept(RECORD)
val nameOffset = in.offset
val name = identForType()
val tparams = typeParams()
val header = formalParams()
val superclass = javaLangRecord() // records always extend java.lang.Record
val interfaces = interfacesOpt() // records may implement interfaces
val (statics, body) = typeBody(RECORD, name, tparams)

// We need to generate accessors for every param, if no method with the same name is already defined

var fieldsByName = header.map(v => (v.name, (v.tpt, v.mods.annotations))).toMap

for case DefDef(name, paramss, tpt, rhs) <- body
if paramss.isEmpty && fieldsByName.contains(name)
do
fieldsByName -= name
end for

val accessors =
(for (name, (tpt, annots)) <- fieldsByName yield
DefDef(name, Nil, tpt, unimplementedExpr).withMods(Modifiers(Flags.JavaDefined | Flags.Method | Flags.Synthetic))
).toList

// generate the canonical constructor
val canonicalConstructor = makeConstructor(header, tparams)

// return the trees, probably with addCompanionObject (like classDecl)
val recordTypeDef = atSpan(start, nameOffset) {
TypeDef(name,
makeTemplate(
parents = superclass :: interfaces,
stats = canonicalConstructor :: accessors ::: body,
tparams = tparams,
false
)
)
}
addCompanionObject(statics, recordTypeDef)
end recordDecl

def interfaceDecl(start: Offset, mods: Modifiers): List[Tree] = {
accept(INTERFACE)
val nameOffset = in.offset
Expand Down Expand Up @@ -846,7 +909,8 @@ object JavaParsers {
else if (in.token == SEMI)
in.nextToken()
else {
if (in.token == ENUM || definesInterface(in.token)) mods |= Flags.JavaStatic
adaptRecordIdentifier()
if (in.token == ENUM || in.token == RECORD || definesInterface(in.token)) mods |= Flags.JavaStatic
val decls = memberDecl(start, mods, parentToken, parentTParams)
(if (mods.is(Flags.JavaStatic) || inInterface && !(decls exists (_.isInstanceOf[DefDef])))
statics
Expand Down Expand Up @@ -947,13 +1011,13 @@ object JavaParsers {
}
}

def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match {
def typeDecl(start: Offset, mods: Modifiers): List[Tree] = in.token match
case ENUM => enumDecl(start, mods)
case INTERFACE => interfaceDecl(start, mods)
case AT => annotationDecl(start, mods)
case CLASS => classDecl(start, mods)
case RECORD => recordDecl(start, mods)
case _ => in.nextToken(); syntaxError(em"illegal start of type declaration", skipIt = true); List(errorTypeTree)
}

def tryConstant: Option[Constant] = {
val negate = in.token match {
Expand Down Expand Up @@ -1004,6 +1068,7 @@ object JavaParsers {
if (in.token != EOF) {
val start = in.offset
val mods = modifiers(inInterface = false)
adaptRecordIdentifier() // needed for typeDecl
buf ++= typeDecl(start, mods)
}
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ object JavaTokens extends TokensCommon {
inline val SWITCH = 133; enter(SWITCH, "switch")
inline val ASSERT = 134; enter(ASSERT, "assert")

/** contextual keywords (turned into keywords in certain conditions, see JLS 3.9 of Java 9+) */
inline val RECORD = 135; enter(RECORD, "record")

/** special symbols */
inline val EQEQ = 140
inline val BANGEQ = 141
Expand Down
15 changes: 15 additions & 0 deletions tests/pos/java-records/FromScala.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
object C:
def useR1 =
// constructor signature
val r = R1(123, "hello")

// accessors
val i: Int = r.i
val s: String = r.s

// methods
val iRes: Int = r.getInt()
val sRes: String = r.getString()

// supertype
val record: java.lang.Record = r
9 changes: 9 additions & 0 deletions tests/pos/java-records/R1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public record R1(int i, String s) {
public String getString() {
return s + i;
}

public int getInt() {
return 0;
}
}

0 comments on commit cb56936

Please sign in to comment.