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

Java parser: add support for JEP-445 #18790

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,26 @@ object JavaParsers {
addCompanionObject(statics, cls)
}

def unnamedClassDecl(priorTypes: List[Tree], firstMemberMods: Modifiers, start: Offset): List[Tree] = {
val name = source.name.replaceAll("\\.java$", "").nn.toTypeName
val (statics, body) = typeBodyDecls(CLASS, name, parentTParams = Nil, firstMemberMods = Some(firstMemberMods))

val priorStatics = priorTypes.map {
case t: (TypeDef | ModuleDef) => t.withMods(t.mods.withFlags(t.mods.flags | Flags.JavaStatic))
case other => throw new Error(s"$other is neither TypeDef nor ModuleDef")
}

val cls = atSpan(start, 0) {
TypeDef(name, makeTemplate(
parents = List(javaLangObject()),
stats = body,
tparams = Nil,
needsDummyConstr = true)
).withMods(Modifiers(Flags.Private | Flags.Final))
}
addCompanionObject(priorStatics ::: statics, cls)
}

def recordDecl(start: Offset, mods: Modifiers): List[Tree] =
accept(RECORD)
val nameOffset = in.offset
Expand Down Expand Up @@ -899,13 +919,13 @@ object JavaParsers {
defs
}

def typeBodyDecls(parentToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = {
def typeBodyDecls(parentToken: Int, parentName: Name, parentTParams: List[TypeDef], firstMemberMods: Option[Modifiers] = None): (List[Tree], List[Tree]) = {
val inInterface = definesInterface(parentToken)
val statics = new ListBuffer[Tree]
val members = new ListBuffer[Tree]
while (in.token != RBRACE && in.token != EOF) {
val start = in.offset
var mods = modifiers(inInterface)
var mods = (if (statics.isEmpty && members.isEmpty) firstMemberMods else None).getOrElse(modifiers(inInterface))
if (in.token == LBRACE) {
skipAhead() // skip init block, we just assume we have seen only static
accept(RBRACE)
Expand Down Expand Up @@ -1067,16 +1087,35 @@ object JavaParsers {
val buf = new ListBuffer[Tree]
while (in.token == IMPORT)
buf ++= importDecl()

val afterImports = in.offset
val typesBuf = new ListBuffer[Tree]

while (in.token != EOF && in.token != RBRACE) {
while (in.token == SEMI) in.nextToken()
if (in.token != EOF) {
val start = in.offset
val mods = modifiers(inInterface = false)
adaptRecordIdentifier() // needed for typeDecl
buf ++= typeDecl(start, mods)

in.token match {
case ENUM | INTERFACE | AT | CLASS | RECORD => typesBuf ++= typeDecl(start, mods)
case _ =>
if (thisPackageName == tpnme.EMPTY_PACKAGE) {
// upon encountering non-types directly at a compilation unit level in an unnamed package,
// the entire compilation unit is treated as a JEP-445 unnamed class
val cls = unnamedClassDecl(priorTypes = typesBuf.toList, firstMemberMods = mods, start = afterImports)
typesBuf.clear()
typesBuf ++= cls
} else {
in.nextToken()
syntaxError(em"illegal start of type declaration", skipIt = true)
List(errorTypeTree)
}
}
}
}
val unit = atSpan(start) { PackageDef(pkg, buf.toList) }
val unit = atSpan(start) { PackageDef(pkg, (buf ++ typesBuf).toList) }
accept(EOF)
unit match
case PackageDef(Ident(nme.EMPTY_PACKAGE), Nil) => EmptyTree
Expand Down
3 changes: 3 additions & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class CompilationTests {
if scala.util.Properties.isJavaAtLeast("16") then
tests ::= compileFilesInDir("tests/pos-java16+", defaultOptions.and("-Ysafe-init"))

if scala.util.Properties.isJavaAtLeast("21") then
tests ::= compileFilesInDir("tests/pos-java21+", defaultOptions.withJavacOnlyOptions("--enable-preview", "--release", "21"))

aggregateTests(tests*).checkCompile()
}

Expand Down
71 changes: 71 additions & 0 deletions compiler/test/dotty/tools/dotc/parsing/JavaJep445ParserTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package dotty.tools.dotc.parsing

import dotty.tools.DottyTest
import dotty.tools.dotc.ast.Trees.{Ident, PackageDef, TypeDef}
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.ast.untpd.ModuleDef
import dotty.tools.dotc.core.Contexts.{Context, ContextBase}
import dotty.tools.dotc.core.StdNames.tpnme
import dotty.tools.dotc.printing.{PlainPrinter, Printer}
import dotty.tools.dotc.util.SourceFile
import dotty.tools.io.PlainFile
import org.junit.Assert.{assertTrue, fail}
import org.junit.Test
import JavaParsers.JavaParser

class JavaJep445ParserTest extends DottyTest {

@Test def `the parser produces same trees for a class and an equivalent unnamed class`
: Unit = {

val unnamed = JavaParser(
SourceFile.virtual(
"MyUnnamed.java",
s"""
|import some.pkg.*;
|
|@interface InnerAnnotation {}
|
|interface InnerInterface {}
|
|@Magic
|public volatile double d;
|
|void main() {}
|
|interface SecondInnerInterface {}
|
|""".stripMargin
)
).parse()

val named = JavaParser(
SourceFile.virtual(
"SomeFile.java",
s"""
|import some.pkg.*;
|
|private final class MyUnnamed {
|
| @interface InnerAnnotation {}
|
| interface InnerInterface {}
|
| @Magic
| public volatile double d;
|
| void main() {}
|
| interface SecondInnerInterface {}
|
|}
|""".stripMargin
)
).parse()

assertTrue(
"expected same trees for named and unnamed classes",
unnamed.sameTree(named)
)
}
}
1 change: 1 addition & 0 deletions tests/pos-java21+/jep445/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class B
1 change: 1 addition & 0 deletions tests/pos-java21+/jep445/UnnamedMainOnly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void main() {}
5 changes: 5 additions & 0 deletions tests/pos-java21+/jep445/UnnamedStartsWithAnnotatedField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

@MyAnnotation
int myInt = 10;

void main() {}
9 changes: 9 additions & 0 deletions tests/pos-java21+/jep445/UnnamedStartsWithField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
private volatile int myInt = 10;

String hello() {
return "hello";
}

interface Inner {}

void main() {}
4 changes: 4 additions & 0 deletions tests/pos-java21+/jep445/UnnamedStartsWithType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

class InnerOfUnnamed {}

void main() {}
Loading