Skip to content

Commit 7076d40

Browse files
Backport "Add support for JEP-409 (sealed classes) + Add javacOpt directive" to LTS (#20943)
Backports #19080 to the LTS branch. PR submitted by the release tooling.
2 parents dc70b77 + aff7b88 commit 7076d40

File tree

15 files changed

+97
-14
lines changed

15 files changed

+97
-14
lines changed

Diff for: compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

+25
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,18 @@ object JavaParsers {
483483
addAnnot(scalaDot(jtpnme.VOLATILEkw))
484484
case SYNCHRONIZED | STRICTFP =>
485485
in.nextToken()
486+
case SEALED =>
487+
flags |= Flags.Sealed
488+
in.nextToken()
489+
// JEP-409: Special trick for the 'non-sealed' java keyword
490+
case IDENTIFIER if in.name.toString == "non" =>
491+
val lookahead = in.LookaheadScanner()
492+
({lookahead.nextToken(); lookahead.token}, {lookahead.nextToken(); lookahead.name.toString}) match
493+
case (MINUS, "sealed") =>
494+
in.nextToken(); in.nextToken() // skip '-' and 'sealed'. Nothing more to do
495+
case _ =>
496+
syntaxError(em"Identifier '${in.name}' is not allowed here")
497+
in.nextToken()
486498
case _ =>
487499
val privateWithin: TypeName =
488500
if (isPackageAccess && !inInterface) thisPackageName
@@ -806,6 +818,17 @@ object JavaParsers {
806818
else
807819
List()
808820

821+
822+
def permittedSubclassesOpt(isSealed: Boolean) : List[Tree] =
823+
if in.token == PERMITS && !isSealed then
824+
syntaxError(em"A type declaration that has a permits clause should have a sealed modifier")
825+
if in.token == PERMITS then
826+
in.nextToken()
827+
repsep(() => typ(), COMMA)
828+
else
829+
// JEP-409: Class/Interface may omit the permits clause
830+
Nil
831+
809832
def classDecl(start: Offset, mods: Modifiers): List[Tree] = {
810833
accept(CLASS)
811834
val nameOffset = in.offset
@@ -819,6 +842,7 @@ object JavaParsers {
819842
else
820843
javaLangObject()
821844
val interfaces = interfacesOpt()
845+
val permittedSubclasses = permittedSubclassesOpt(mods.is(Flags.Sealed))
822846
val (statics, body) = typeBody(CLASS, name, tparams)
823847
val cls = atSpan(start, nameOffset) {
824848
TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, true)).withMods(mods)
@@ -883,6 +907,7 @@ object JavaParsers {
883907
}
884908
else
885909
List(javaLangObject())
910+
val permittedSubclasses = permittedSubclassesOpt(mods is Flags.Sealed)
886911
val (statics, body) = typeBody(INTERFACE, name, tparams)
887912
val iface = atSpan(start, nameOffset) {
888913
TypeDef(

Diff for: compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala

-1
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,6 @@ object JavaScanners {
393393
'5' | '6' | '7' | '8' | '9' =>
394394
putChar(ch)
395395
nextChar()
396-
397396
case '_' =>
398397
putChar(ch)
399398
nextChar()

Diff for: compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object JavaTokens extends TokensCommon {
1010

1111
final val javaOnlyKeywords: TokenSet = tokenRange(INSTANCEOF, ASSERT)
1212
final val sharedKeywords: BitSet = BitSet( IF, FOR, ELSE, THIS, NULL, NEW, SUPER, ABSTRACT, FINAL, PRIVATE, PROTECTED,
13-
EXTENDS, TRUE, FALSE, CLASS, IMPORT, PACKAGE, DO, THROW, TRY, CATCH, FINALLY, WHILE, RETURN )
13+
EXTENDS, TRUE, FALSE, CLASS, IMPORT, PACKAGE, DO, THROW, TRY, CATCH, FINALLY, WHILE, RETURN, SEALED)
1414
final val primTypes: TokenSet = tokenRange(VOID, DOUBLE)
1515
final val keywords: BitSet = sharedKeywords | javaOnlyKeywords | primTypes
1616

@@ -22,6 +22,7 @@ object JavaTokens extends TokensCommon {
2222
inline val INTERFACE = 105; enter(INTERFACE, "interface")
2323
inline val ENUM = 106; enter(ENUM, "enum")
2424
inline val IMPLEMENTS = 107; enter(IMPLEMENTS, "implements")
25+
inline val PERMITS = 108; enter(PERMITS, "permits")
2526

2627
/** modifiers */
2728
inline val PUBLIC = 110; enter(PUBLIC, "public")

Diff for: compiler/src/dotty/tools/dotc/parsing/Tokens.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ abstract class TokensCommon {
7878
//inline val YIELD = 48; enter(YIELD, "yield")
7979
inline val DO = 49; enter(DO, "do")
8080
//inline val TRAIT = 50; enter(TRAIT, "trait")
81-
//inline val SEALED = 51; enter(SEALED, "sealed")
81+
inline val SEALED = 51; enter(SEALED, "sealed")
8282
inline val THROW = 52; enter(THROW, "throw")
8383
inline val TRY = 53; enter(TRY, "try")
8484
inline val CATCH = 54; enter(CATCH, "catch")
@@ -169,7 +169,7 @@ object Tokens extends TokensCommon {
169169
inline val OBJECT = 44; enter(OBJECT, "object")
170170
inline val YIELD = 48; enter(YIELD, "yield")
171171
inline val TRAIT = 50; enter(TRAIT, "trait")
172-
inline val SEALED = 51; enter(SEALED, "sealed")
172+
//inline val SEALED = 51; enter(SEALED, "sealed")
173173
inline val MATCH = 58; enter(MATCH, "match")
174174
inline val LAZY = 59; enter(LAZY, "lazy")
175175
inline val THEN = 60; enter(THEN, "then")

Diff for: compiler/src/dotty/tools/dotc/typer/Namer.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -1543,6 +1543,7 @@ class Namer { typer: Typer =>
15431543
* (2) If may not derive from itself
15441544
* (3) The class is not final
15451545
* (4) If the class is sealed, it is defined in the same compilation unit as the current class
1546+
* (unless defined in Java. See JEP-409)
15461547
*
15471548
* @param isJava If true, the parent type is in Java mode, and we do not require a stable prefix
15481549
*/
@@ -1566,7 +1567,7 @@ class Namer { typer: Typer =>
15661567
if pclazz.is(Final) then
15671568
report.error(ExtendFinalClass(cls, pclazz), cls.srcPos)
15681569
else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then
1569-
if pclazz.is(Sealed) then
1570+
if pclazz.is(Sealed) && !pclazz.is(JavaDefined) then
15701571
report.error(UnableToExtendSealedClass(pclazz), cls.srcPos)
15711572
else if sourceVersion.isAtLeast(future) then
15721573
checkFeature(nme.adhocExtensions,

Diff for: compiler/test/dotty/tools/utils.scala

+15-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,14 @@ def toolArgsFor(tool: ToolName, filename: Option[String])(lines: List[String]):
8181
// groups are (name, args)
8282
// note: ideally we would replace everything that requires this to use directive syntax, however scalajs: --skip has no directive equivalent yet.
8383
private val toolArg = raw"(?://|/\*| \*) ?(?i:(${ToolName.values.mkString("|")})):((?:[^*]|\*(?!/))*)".r.unanchored
84+
85+
// ================================================================================================
86+
// =================================== VULPIX DIRECTIVES ==========================================
87+
// ================================================================================================
88+
89+
/** Directive to specify to vulpix the options to pass to Dotty */
8490
private val directiveOptionsArg = raw"//> using options (.*)".r.unanchored
91+
private val directiveJavacOptions = raw"//> using javacOpt (.*)".r.unanchored
8592

8693
// Inspect the lines for compiler options of the form
8794
// `//> using options args`, `// scalajs: args`, `/* scalajs: args`, ` * scalajs: args` etc.
@@ -90,10 +97,15 @@ private val directiveOptionsArg = raw"//> using options (.*)".r.unanchored
9097
def toolArgsParse(lines: List[String], filename: Option[String]): List[(String,String)] =
9198
lines.flatMap {
9299
case toolArg("scalac", _) => sys.error(s"`// scalac: args` not supported. Please use `//> using options args`${filename.fold("")(f => s" in file $f")}")
100+
case toolArg("javac", _) => sys.error(s"`// javac: args` not supported. Please use `//> using javacOpt args`${filename.fold("")(f => s" in file $f")}")
93101
case toolArg(name, args) => List((name, args))
94102
case _ => Nil
95103
} ++
96-
lines.flatMap { case directiveOptionsArg(args) => List(("scalac", args)) case _ => Nil }
104+
lines.flatMap {
105+
case directiveOptionsArg(args) => List(("scalac", args))
106+
case directiveJavacOptions(args) => List(("javac", args))
107+
case _ => Nil
108+
}
97109

98110
import org.junit.Test
99111
import org.junit.Assert._
@@ -104,6 +116,6 @@ class ToolArgsTest:
104116
@Test def `tool is present`: Unit = assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: Nil))
105117
@Test def `missing tool is absent`: Unit = assertEquals(Nil, toolArgsFor(ToolName.Javac, None)("// test: -hey" :: Nil))
106118
@Test def `multitool is present`: Unit =
107-
assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: "// javac: -d /tmp" :: Nil))
108-
assertEquals("-d" :: "/tmp" :: Nil, toolArgsFor(ToolName.Javac, None)("// test: -hey" :: "// javac: -d /tmp" :: Nil))
119+
assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: "// java: -d /tmp" :: Nil))
120+
assertEquals("-d" :: "/tmp" :: Nil, toolArgsFor(ToolName.Java, None)("// test: -hey" :: "// java: -d /tmp" :: Nil))
109121
end ToolArgsTest

Diff for: compiler/test/dotty/tools/vulpix/ParallelTesting.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
498498
case None => true
499499

500500
def scalacOptions = toolArgs.getOrElse(ToolName.Scalac, Nil)
501+
def javacOptions = toolArgs.getOrElse(ToolName.Javac, Nil)
501502

502503
val flags = flags0
503504
.and(scalacOptions: _*)
@@ -512,11 +513,10 @@ trait ParallelTesting extends RunnerOrchestration { self =>
512513

513514
def compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) {
514515
val fullArgs = Array(
515-
"javac",
516516
"-encoding", StandardCharsets.UTF_8.name,
517-
) ++ flags.javacFlags ++ fs
517+
) ++ flags.javacFlags ++ javacOptions++ fs
518518

519-
val process = Runtime.getRuntime.exec(fullArgs)
519+
val process = Runtime.getRuntime.exec("javac" +: fullArgs)
520520
val output = Source.fromInputStream(process.getErrorStream).mkString
521521

522522
if waitForJudiciously(process) != 0 then Some(output)

Diff for: tests/neg/i18533.check

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Error: tests/neg/i18533/Pet_SCALA_ONLY.java:3:10 --------------------------------------------------------------------
2+
3 |class Pet permits Cat { // error
3+
| ^^^^^^^
4+
| A type declaration that has a permits clause should have a sealed modifier
5+
-- Error: tests/neg/i18533/non-SCALA_ONLY.java:4:7 ---------------------------------------------------------------------
6+
4 |public non class Test { // error
7+
| ^^^
8+
| Identifier 'non' is not allowed here

Diff for: tests/neg/i18533/Cat_SCALA_ONLY.java

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package i18533;
2+
3+
public final class Cat extends Pet {
4+
5+
}

Diff for: tests/neg/i18533/Pet_SCALA_ONLY.java

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package i18533;
2+
3+
class Pet permits Cat { // error
4+
5+
}

Diff for: tests/neg/i18533/non-SCALA_ONLY.java

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package i18533;
2+
3+
// Special test for the non-sealed trick (See JavaParsers.scala::modifiers)
4+
public non class Test { // error
5+
6+
}

Diff for: tests/pos/i18533/Cat.java

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using javacOpt --enable-preview --source 17
2+
//> test: -jvm 17+
3+
4+
package i18533;
5+
6+
public final class Cat extends Pet {
7+
8+
}

Diff for: tests/pos/i18533/Dog.java

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using javacOpt --enable-preview --source 17
2+
//> test: -jvm 17+
3+
4+
package i18533;
5+
6+
public non-sealed class Dog extends Pet {
7+
8+
}

Diff for: tests/pos/i18533/Pet.java

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using javacOpt --enable-preview --source 17
2+
//> test: -jvm 17+
3+
4+
package i18533;
5+
6+
public sealed class Pet permits Cat, Dog {
7+
8+
}

Diff for: tests/run/t9915/C_1.java

-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
/*
2-
* javac: -encoding UTF-8
3-
*/
41
public class C_1 {
52
public static final String NULLED = "X\000ABC";
63
public static final String SUPPED = "𐒈𐒝𐒑𐒛𐒐𐒘𐒕𐒖";

0 commit comments

Comments
 (0)