Skip to content

Commit a55e5bc

Browse files
committed
Java parser: add support for unnamed classes (JEP-445)
Fixes scala#18584
1 parent c0eae68 commit a55e5bc

File tree

8 files changed

+137
-4
lines changed

8 files changed

+137
-4
lines changed

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

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,26 @@ object JavaParsers {
826826
addCompanionObject(statics, cls)
827827
}
828828

829+
def unnamedClassDecl(priorTypes: List[Tree], firstMemberMods: Modifiers, start: Offset): List[Tree] = {
830+
val name = source.name.replaceAll("\\.java$", "").nn.toTypeName
831+
val (statics, body) = typeBodyDecls(CLASS, name, parentTParams = Nil, firstMemberMods = Some(firstMemberMods))
832+
833+
val priorStatics = priorTypes.map {
834+
case t: (TypeDef | ModuleDef) => t.withMods(t.mods.withFlags(t.mods.flags | Flags.JavaStatic))
835+
case x => x //TODO bug
836+
}
837+
838+
val cls = atSpan(start, 0) {
839+
TypeDef(name, makeTemplate(
840+
parents = List(javaLangObject()),
841+
stats = body,
842+
tparams = Nil,
843+
needsDummyConstr = true)
844+
).withMods(Modifiers(Flags.Private | Flags.Final))
845+
}
846+
addCompanionObject(priorStatics ::: statics, cls)
847+
}
848+
829849
def recordDecl(start: Offset, mods: Modifiers): List[Tree] =
830850
accept(RECORD)
831851
val nameOffset = in.offset
@@ -899,13 +919,13 @@ object JavaParsers {
899919
defs
900920
}
901921

902-
def typeBodyDecls(parentToken: Int, parentName: Name, parentTParams: List[TypeDef]): (List[Tree], List[Tree]) = {
922+
def typeBodyDecls(parentToken: Int, parentName: Name, parentTParams: List[TypeDef], firstMemberMods: Option[Modifiers] = None): (List[Tree], List[Tree]) = {
903923
val inInterface = definesInterface(parentToken)
904924
val statics = new ListBuffer[Tree]
905925
val members = new ListBuffer[Tree]
906926
while (in.token != RBRACE && in.token != EOF) {
907927
val start = in.offset
908-
var mods = modifiers(inInterface)
928+
var mods = (if (statics.isEmpty && members.isEmpty) firstMemberMods else None).getOrElse(modifiers(inInterface))
909929
if (in.token == LBRACE) {
910930
skipAhead() // skip init block, we just assume we have seen only static
911931
accept(RBRACE)
@@ -1067,16 +1087,35 @@ object JavaParsers {
10671087
val buf = new ListBuffer[Tree]
10681088
while (in.token == IMPORT)
10691089
buf ++= importDecl()
1090+
1091+
val afterImports = in.offset
1092+
val typesBuf = new ListBuffer[Tree]
1093+
10701094
while (in.token != EOF && in.token != RBRACE) {
10711095
while (in.token == SEMI) in.nextToken()
10721096
if (in.token != EOF) {
10731097
val start = in.offset
10741098
val mods = modifiers(inInterface = false)
10751099
adaptRecordIdentifier() // needed for typeDecl
1076-
buf ++= typeDecl(start, mods)
1100+
1101+
in.token match {
1102+
case ENUM | INTERFACE | AT | CLASS | RECORD => typesBuf ++= typeDecl(start, mods)
1103+
case _ =>
1104+
if (thisPackageName == tpnme.EMPTY_PACKAGE) {
1105+
// upon encountering non-types directly at a compilation unit level in an unnamed package,
1106+
// the entire compilation unit is treated as a JEP-445 unnamed class
1107+
val cls = unnamedClassDecl(priorTypes = typesBuf.toList, firstMemberMods = mods, start = afterImports)
1108+
typesBuf.clear()
1109+
typesBuf ++= cls
1110+
} else {
1111+
in.nextToken()
1112+
syntaxError(em"illegal start of type declaration", skipIt = true)
1113+
List(errorTypeTree)
1114+
}
1115+
}
10771116
}
10781117
}
1079-
val unit = atSpan(start) { PackageDef(pkg, buf.toList) }
1118+
val unit = atSpan(start) { PackageDef(pkg, (buf ++ typesBuf).toList) }
10801119
accept(EOF)
10811120
unit match
10821121
case PackageDef(Ident(nme.EMPTY_PACKAGE), Nil) => EmptyTree

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class CompilationTests {
5050
if scala.util.Properties.isJavaAtLeast("16") then
5151
tests ::= compileFilesInDir("tests/pos-java16+", defaultOptions.and("-Ysafe-init"))
5252

53+
if scala.util.Properties.isJavaAtLeast("21") then
54+
tests ::= compileFilesInDir("tests/pos-java21+", defaultOptions.withJavacOnlyOptions("--enable-preview", "--release", "21"))
55+
5356
aggregateTests(tests*).checkCompile()
5457
}
5558

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package dotty.tools.dotc.parsing
2+
3+
import dotty.tools.DottyTest
4+
import dotty.tools.dotc.ast.Trees.{Ident, PackageDef, TypeDef}
5+
import dotty.tools.dotc.ast.untpd
6+
import dotty.tools.dotc.ast.untpd.ModuleDef
7+
import dotty.tools.dotc.core.Contexts.{Context, ContextBase}
8+
import dotty.tools.dotc.core.StdNames.tpnme
9+
import dotty.tools.dotc.printing.{PlainPrinter, Printer}
10+
import dotty.tools.dotc.util.SourceFile
11+
import dotty.tools.io.PlainFile
12+
import org.junit.Assert.{assertTrue, fail}
13+
import org.junit.Test
14+
import JavaParsers.JavaParser
15+
16+
class JavaJep445ParserTest extends DottyTest {
17+
18+
@Test def `the parser produces same trees for a class and an equivalent unnamed class`
19+
: Unit = {
20+
21+
val unnamed = JavaParser(
22+
SourceFile.virtual(
23+
"MyUnnamed.java",
24+
s"""
25+
|import some.pkg.*;
26+
|
27+
|@interface InnerAnnotation {}
28+
|
29+
|interface InnerInterface {}
30+
|
31+
|@Magic
32+
|public volatile double d;
33+
|
34+
|void main() {}
35+
|
36+
|interface SecondInnerInterface {}
37+
|
38+
|""".stripMargin
39+
)
40+
).parse()
41+
42+
val named = JavaParser(
43+
SourceFile.virtual(
44+
"SomeFile.java",
45+
s"""
46+
|import some.pkg.*;
47+
|
48+
|private final class MyUnnamed {
49+
|
50+
| @interface InnerAnnotation {}
51+
|
52+
| interface InnerInterface {}
53+
|
54+
| @Magic
55+
| public volatile double d;
56+
|
57+
| void main() {}
58+
|
59+
| interface SecondInnerInterface {}
60+
|
61+
|}
62+
|""".stripMargin
63+
)
64+
).parse()
65+
66+
assertTrue(
67+
"expected same trees for named and unnamed classes",
68+
unnamed.sameTree(named)
69+
)
70+
}
71+
}

tests/pos-java21+/jep445/B.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class B
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
@MyAnnotation
3+
int myInt = 10;
4+
5+
void main() {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
private volatile int myInt = 10;
2+
3+
String hello() {
4+
return "hello";
5+
}
6+
7+
interface Inner {}
8+
9+
void main() {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
class InnerOfUnnamed {}
3+
4+
void main() {}

0 commit comments

Comments
 (0)