Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 01540b0

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

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 other => throw new Error(s"$other is neither TypeDef nor ModuleDef")
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 elseNone).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)

AltStyle によって変換されたページ (->オリジナル) /