Skip to content

Commit

Permalink
Java parser: add support for unnamed classes (JEP-445)
Browse files Browse the repository at this point in the history
Fixes #18584
  • Loading branch information
povder committed Nov 4, 2023
1 parent c0eae68 commit e9b364a
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 2 deletions.
46 changes: 44 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,27 @@ object JavaParsers {
addCompanionObject(statics, cls)
}

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

val (priorStatics, priorBody) = priorTypes.partition {
case t: TypeDef => t.mods.is(Flags.JavaStatic)
case _: ModuleDef => true
case _ => false
}

val cls = atSpan(start, 0) {
TypeDef(name, makeTemplate(
parents = Nil,
stats = priorBody ::: 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 @@ -1067,16 +1088,37 @@ 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
//TODO support @annotated members of unnamed class
val cls = unnamedClassDecl(priorTypes = typesBuf.toList, 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
82 changes: 82 additions & 0 deletions compiler/test/dotty/tools/dotc/parsing/JavaJep445ParserTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package dotty.tools.dotc.parsing

import dotty.tools.DottyTest
import dotty.tools.dotc.ast.Trees.{Ident, PackageDef, TypeDef}
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.fail
import org.junit.Test

class JavaJep445ParserTest extends DottyTest {

@Test def `parses unnamed class`: Unit = {
val code =
s"""
|// hello
|
|import some.pkg.*;
|
|private volatile int x = 0;
|@Magic
|protected String s = "s";
|
|void main() {}
|""".stripMargin

val parser =
JavaParsers.JavaParser(SourceFile.virtual("MyUnnamed.java", code))
val tree = parser.parse()

println(tree.show)

fail("TODO")
}

@Test def `treats leading top-level types as nested types of unnamed class`: Unit = {
val code =
s"""
|// hello
|
|import some.pkg.*;
|
|interface Inner {}
|
|static class InnerStatic {}
|
|void main() {}
|""".stripMargin

val parser =
JavaParsers.JavaParser(SourceFile.virtual("MyUnnamed.java", code))
val tree = parser.parse()

println(tree.show)

fail("TODO")
}

@Test def `treats leading top-level annotated vars as members of unnamed class`: Unit = {
val code =
s"""
|
|import some.pkg.*;
|
|@MyAnnotation
|int x = 0;
|
|void main() {}
|""".stripMargin

val parser =
JavaParsers.JavaParser(SourceFile.virtual("MyUnnamed.java", code))
val tree = parser.parse()

println(tree.show)

fail("TODO")
}
}
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() {}

0 comments on commit e9b364a

Please sign in to comment.