Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JDK 16 records in Java sources #9551

Merged
merged 5 commits into from Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
109 changes: 90 additions & 19 deletions src/compiler/scala/tools/nsc/javac/JavaParsers.scala
Expand Up @@ -118,6 +118,8 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {

def javaLangObject(): Tree = javaLangDot(tpnme.Object)

def javaLangRecord(): Tree = javaLangDot(tpnme.Record)

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

Expand Down Expand Up @@ -564,6 +566,16 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {

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

/** If the next token is the identifier "record", convert it into a proper
* token. Technically, "record" is just a restricted identifier. However,
* once we've figured out that it is in a position where it identifies a
* "record" class, it is much more convenient to promote it to a token.
*/
def adaptRecordIdentifier(): Unit = {
if (in.token == IDENTIFIER && in.name == nme.javaRestrictedIdentifiers.RECORD)
in.token = RECORD
}

def termDecl(mods: Modifiers, parentToken: Int): List[Tree] = {
val inInterface = definesInterface(parentToken)
val tparams = if (in.token == LT) typeParams() else List()
Expand All @@ -587,6 +599,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
DefDef(mods, nme.CONSTRUCTOR, tparams, List(vparams), TypeTree(), methodBody())
}
}
} else if (in.token == LBRACE && rtptName != nme.EMPTY && parentToken == RECORD) {
// compact constructor
methodBody()
List.empty
} else {
var mods1 = mods
if (mods hasFlag Flags.ABSTRACT) mods1 = mods &~ Flags.ABSTRACT | Flags.DEFERRED
Expand Down Expand Up @@ -721,11 +737,13 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}
}

def memberDecl(mods: Modifiers, parentToken: Int): List[Tree] = in.token match {
case CLASS | ENUM | INTERFACE | AT =>
typeDecl(if (definesInterface(parentToken)) mods | Flags.STATIC else mods)
case _ =>
termDecl(mods, parentToken)
def memberDecl(mods: Modifiers, parentToken: Int): List[Tree] = {
in.token match {
case CLASS | ENUM | RECORD | INTERFACE | AT =>
typeDecl(mods)
case _ =>
termDecl(mods, parentToken)
lrytz marked this conversation as resolved.
Show resolved Hide resolved
}
}

def makeCompanionObject(cdef: ClassDef, statics: List[Tree]): Tree =
Expand Down Expand Up @@ -802,12 +820,57 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
javaLangObject()
}
val interfaces = interfacesOpt()
val (statics, body) = typeBody(CLASS, name)
val (statics, body) = typeBody(CLASS)
addCompanionObject(statics, atPos(pos) {
ClassDef(mods, name, tparams, makeTemplate(superclass :: interfaces, body))
})
}

def recordDecl(mods: Modifiers): List[Tree] = {
harpocrates marked this conversation as resolved.
Show resolved Hide resolved
accept(RECORD)
val pos = in.currentPos
val name = identForType()
val tparams = typeParams()
val header = formalParams()
val superclass = javaLangRecord()
val interfaces = interfacesOpt()
val (statics, body) = typeBody(RECORD)

// Generate accessors, if not already manually specified
var generateAccessors = header
.view
.map { case ValDef(mods, name, tpt, _) => (name, (tpt, mods.annotations)) }
.toMap
for (DefDef(_, name, List(), List(params), _, _) <- body if generateAccessors.contains(name) && params.isEmpty)
generateAccessors -= name

val accessors = generateAccessors
.map { case (name, (tpt, annots)) =>
DefDef(Modifiers(Flags.JAVA) withAnnotations annots, name, List(), List(), tpt.duplicate, blankExpr)
}
.toList

// Generate canonical constructor. During parsing this is done unconditionally but the symbol
// is unlinked in Namer if it is found to clash with a manually specified constructor.
val canonicalCtor = DefDef(
mods | Flags.SYNTHETIC,
nme.CONSTRUCTOR,
List(),
List(header.map(_.duplicate)),
TypeTree(),
blankExpr
)

addCompanionObject(statics, atPos(pos) {
ClassDef(
mods | Flags.FINAL,
name,
tparams,
makeTemplate(superclass :: interfaces, canonicalCtor :: accessors ::: body)
)
})
}

def interfaceDecl(mods: Modifiers): List[Tree] = {
accept(INTERFACE)
val pos = in.currentPos
Expand All @@ -820,22 +883,22 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
} else {
List(javaLangObject())
}
val (statics, body) = typeBody(INTERFACE, name)
val (statics, body) = typeBody(INTERFACE)
addCompanionObject(statics, atPos(pos) {
ClassDef(mods | Flags.TRAIT | Flags.INTERFACE | Flags.ABSTRACT,
name, tparams,
makeTemplate(parents, body))
})
}

def typeBody(leadingToken: Int, parentName: Name): (List[Tree], List[Tree]) = {
def typeBody(leadingToken: Int): (List[Tree], List[Tree]) = {
accept(LBRACE)
val defs = typeBodyDecls(leadingToken, parentName)
val defs = typeBodyDecls(leadingToken)
accept(RBRACE)
defs
}

def typeBodyDecls(parentToken: Int, parentName: Name): (List[Tree], List[Tree]) = {
def typeBodyDecls(parentToken: Int): (List[Tree], List[Tree]) = {
val inInterface = definesInterface(parentToken)
val statics = new ListBuffer[Tree]
val members = new ListBuffer[Tree]
Expand All @@ -847,7 +910,11 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
} else if (in.token == SEMI) {
in.nextToken()
} else {
if (in.token == ENUM || definesInterface(in.token)) mods |= Flags.STATIC

// See "14.3. Local Class and Interface Declarations"
adaptRecordIdentifier()
if (in.token == ENUM || in.token == RECORD || definesInterface(in.token))
mods |= Flags.STATIC
val decls = joinComment(memberDecl(mods, parentToken))

@tailrec
Expand All @@ -871,7 +938,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
accept(INTERFACE)
val pos = in.currentPos
val name = identForType()
val (statics, body) = typeBody(AT, name)
val (statics, body) = typeBody(AT)
val templ = makeTemplate(annotationParents, body)
addCompanionObject(statics, atPos(pos) {
import Flags._
Expand Down Expand Up @@ -908,7 +975,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
val (statics, body) =
if (in.token == SEMI) {
in.nextToken()
typeBodyDecls(ENUM, name)
typeBodyDecls(ENUM)
} else {
(List(), List())
}
Expand Down Expand Up @@ -956,12 +1023,16 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
(res, hasClassBody)
}

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

def tryLiteral(negate: Boolean = false): Option[Constant] = {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/javac/JavaTokens.scala
Expand Up @@ -20,6 +20,7 @@ object JavaTokens extends ast.parser.CommonTokens {

/** identifiers */
final val IDENTIFIER = 10
final val RECORD = 12 // restricted identifier, so not lexed directly
def isIdentifier(code: Int) =
code == IDENTIFIER

Expand Down
8 changes: 7 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -1487,7 +1487,13 @@ trait Namers extends MethodSynthesis {
}

val methSig = deskolemizedPolySig(vparamSymssOrEmptyParamsFromOverride, resTp)
pluginsTypeSig(methSig, typer, ddef, resTpGiven)
val unlink = methOwner.isJava && meth.isSynthetic && meth.isConstructor && methOwner.superClass == JavaRecordClass &&
methOwner.info.decl(meth.name).alternatives.exists(c => c != meth && c.tpe.matches(methSig))
if (unlink) {
methOwner.info.decls.unlink(meth)
ErrorType
} else
pluginsTypeSig(methSig, typer, ddef, resTpGiven)
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/Definitions.scala
Expand Up @@ -411,6 +411,7 @@ trait Definitions extends api.StandardDefinitions {
lazy val JavaEnumClass = requiredClass[java.lang.Enum[_]]
lazy val JavaUtilMap = requiredClass[java.util.Map[_, _]]
lazy val JavaUtilHashMap = requiredClass[java.util.HashMap[_, _]]
lazy val JavaRecordClass = getClassIfDefined("java.lang.Record")

lazy val ByNameParamClass = specialPolyClass(tpnme.BYNAME_PARAM_CLASS_NAME, COVARIANT)(_ => AnyTpe)
lazy val JavaRepeatedParamClass = specialPolyClass(tpnme.JAVA_REPEATED_PARAM_CLASS_NAME, COVARIANT)(tparam => arrayType(tparam.tpe))
Expand Down
11 changes: 11 additions & 0 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -236,6 +236,7 @@ trait StdNames {
final val keywords = kw.result
} with CommonNames {
final val javaKeywords = new JavaKeywords()
final val javaRestrictedIdentifiers = new JavaRestrictedIdentifiers()
}

abstract class TypeNames extends Keywords with TypeNamesApi {
Expand Down Expand Up @@ -264,6 +265,7 @@ trait StdNames {
final val Object: NameType = nameType("Object")
final val PrefixType: NameType = nameType("PrefixType")
final val Product: NameType = nameType("Product")
final val Record: NameType = nameType("Record")
final val Serializable: NameType = nameType("Serializable")
final val Singleton: NameType = nameType("Singleton")
final val Throwable: NameType = nameType("Throwable")
Expand Down Expand Up @@ -1255,6 +1257,15 @@ trait StdNames {
final val keywords = kw.result
}

// "The identifiers var, yield, and record are restricted identifiers because they are not allowed in some contexts"
// A type identifier is an identifier that is not the character sequence var, yield, or record.
// An unqualified method identifier is an identifier that is not the character sequence yield.
class JavaRestrictedIdentifiers {
final val RECORD: TermName = TermName("record")
final val VAR: TermName = TermName("var")
final val YIELD: TermName = TermName("yield")
}

sealed abstract class SymbolNames {
protected def nameType(s: String): TypeName = newTypeNameCached(s)

Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Expand Up @@ -290,6 +290,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
definitions.JavaEnumClass
definitions.JavaUtilMap
definitions.JavaUtilHashMap
definitions.JavaRecordClass
definitions.ByNameParamClass
definitions.JavaRepeatedParamClass
definitions.RepeatedParamClass
Expand Down
55 changes: 55 additions & 0 deletions test/files/pos/t11908/C.scala
@@ -0,0 +1,55 @@
// javaVersion: 16+
object C {

def useR1 = {
// constructor signature
val r1 = new R1(123, "hello")

// accessors signature
val i: Int = r1.i
val s: String = r1.s

// method
val s2: String = r1.someMethod()

// supertype
val isRecord: java.lang.Record = r1

()
}

def useR2 = {
// constructor signature
val r2 = new R2.R(123, "hello")

// accessors signature
val i: Int = r2.i
val s: String = r2.s

// method
val i2: Int = r2.getInt

// supertype
val isIntLike: IntLike = r2
val isRecord: java.lang.Record = r2

()
}

def useR3 = {
// constructor signature
val r3 = new R3(123, 42L, "hi")
new R3("hi", 123)

// accessors signature
val i: Int = r3.i
val l: Long = r3.l
val s: String = r3.s

// method
val l2: Long = r3.l(43L, 44L)

// supertype
val isRecord: java.lang.Record = r3
}
}
4 changes: 4 additions & 0 deletions test/files/pos/t11908/IntLike.scala
@@ -0,0 +1,4 @@
// javaVersion: 16+
trait IntLike {
def getInt: Int
}
7 changes: 7 additions & 0 deletions test/files/pos/t11908/R1.java
@@ -0,0 +1,7 @@
// javaVersion: 16+
record R1(int i, String s) {
harpocrates marked this conversation as resolved.
Show resolved Hide resolved

public String someMethod() {
return s + "!";
}
}
14 changes: 14 additions & 0 deletions test/files/pos/t11908/R2.java
@@ -0,0 +1,14 @@
// javaVersion: 16+
public class R2 {
final record R(int i, String s) implements IntLike {
public int getInt() {
return i;
}

// Canonical constructor
public R(int i, java.lang.String s) {
this.i = i;
this.s = s.intern();
}
}
}
23 changes: 23 additions & 0 deletions test/files/pos/t11908/R3.java
@@ -0,0 +1,23 @@
// javaVersion: 16+
public record R3(int i, long l, String s) {

// User-specified accessor
public int i() {
return i + 1; // evil >:)
}

// Not an accessor - too many parameters
public long l(long a1, long a2) {
return a1 + a2;
}

// Secondary constructor
public R3(String s, int i) {
this(i, 42L, s);
}

// Compact constructor
public R3 {
s = s.intern();
}
}