diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index d6fe624bd0fb..b9edb147be9d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -483,6 +483,18 @@ object JavaParsers { addAnnot(scalaDot(jtpnme.VOLATILEkw)) case SYNCHRONIZED | STRICTFP => in.nextToken() + case SEALED => + flags |= Flags.Sealed + in.nextToken() + // JEP-409: Special trick for the 'non-sealed' java keyword + case IDENTIFIER if in.name.toString == "non" => + val lookahead = in.LookaheadScanner() + ({lookahead.nextToken(); lookahead.token}, {lookahead.nextToken(); lookahead.name.toString}) match + case (MINUS, "sealed") => + in.nextToken(); in.nextToken() // skip '-' and 'sealed'. Nothing more to do + case _ => + syntaxError(em"Identifier '${in.name}' is not allowed here") + in.nextToken() case _ => val privateWithin: TypeName = if (isPackageAccess && !inInterface) thisPackageName @@ -806,6 +818,17 @@ object JavaParsers { else List() + + def permittedSubclassesOpt(isSealed: Boolean) : List[Tree] = + if in.token == PERMITS && !isSealed then + syntaxError(em"A type declaration that has a permits clause should have a sealed modifier") + if in.token == PERMITS then + in.nextToken() + repsep(() => typ(), COMMA) + else + // JEP-409: Class/Interface may omit the permits clause + Nil + def classDecl(start: Offset, mods: Modifiers): List[Tree] = { accept(CLASS) val nameOffset = in.offset @@ -819,6 +842,7 @@ object JavaParsers { else javaLangObject() val interfaces = interfacesOpt() + val permittedSubclasses = permittedSubclassesOpt(mods.is(Flags.Sealed)) val (statics, body) = typeBody(CLASS, name, tparams) val cls = atSpan(start, nameOffset) { TypeDef(name, makeTemplate(superclass :: interfaces, body, tparams, true)).withMods(mods) @@ -883,6 +907,7 @@ object JavaParsers { } else List(javaLangObject()) + val permittedSubclasses = permittedSubclassesOpt(mods is Flags.Sealed) val (statics, body) = typeBody(INTERFACE, name, tparams) val iface = atSpan(start, nameOffset) { TypeDef( diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala index f50dcdda438c..3f993195e4f3 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala @@ -393,7 +393,6 @@ object JavaScanners { '5' | '6' | '7' | '8' | '9' => putChar(ch) nextChar() - case '_' => putChar(ch) nextChar() diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala b/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala index 2b7882173e00..5fd177f384ae 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaTokens.scala @@ -10,7 +10,7 @@ object JavaTokens extends TokensCommon { final val javaOnlyKeywords: TokenSet = tokenRange(INSTANCEOF, ASSERT) final val sharedKeywords: BitSet = BitSet( IF, FOR, ELSE, THIS, NULL, NEW, SUPER, ABSTRACT, FINAL, PRIVATE, PROTECTED, - EXTENDS, TRUE, FALSE, CLASS, IMPORT, PACKAGE, DO, THROW, TRY, CATCH, FINALLY, WHILE, RETURN ) + EXTENDS, TRUE, FALSE, CLASS, IMPORT, PACKAGE, DO, THROW, TRY, CATCH, FINALLY, WHILE, RETURN, SEALED) final val primTypes: TokenSet = tokenRange(VOID, DOUBLE) final val keywords: BitSet = sharedKeywords | javaOnlyKeywords | primTypes @@ -22,6 +22,7 @@ object JavaTokens extends TokensCommon { inline val INTERFACE = 105; enter(INTERFACE, "interface") inline val ENUM = 106; enter(ENUM, "enum") inline val IMPLEMENTS = 107; enter(IMPLEMENTS, "implements") + inline val PERMITS = 108; enter(PERMITS, "permits") /** modifiers */ inline val PUBLIC = 110; enter(PUBLIC, "public") diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 2127aea5533b..3a8faaf57928 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -78,7 +78,7 @@ abstract class TokensCommon { //inline val YIELD = 48; enter(YIELD, "yield") inline val DO = 49; enter(DO, "do") //inline val TRAIT = 50; enter(TRAIT, "trait") - //inline val SEALED = 51; enter(SEALED, "sealed") + inline val SEALED = 51; enter(SEALED, "sealed") inline val THROW = 52; enter(THROW, "throw") inline val TRY = 53; enter(TRY, "try") inline val CATCH = 54; enter(CATCH, "catch") @@ -169,7 +169,7 @@ object Tokens extends TokensCommon { inline val OBJECT = 44; enter(OBJECT, "object") inline val YIELD = 48; enter(YIELD, "yield") inline val TRAIT = 50; enter(TRAIT, "trait") - inline val SEALED = 51; enter(SEALED, "sealed") + //inline val SEALED = 51; enter(SEALED, "sealed") inline val MATCH = 58; enter(MATCH, "match") inline val LAZY = 59; enter(LAZY, "lazy") inline val THEN = 60; enter(THEN, "then") diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e9b00cf95cac..af334ba4b498 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1543,6 +1543,7 @@ class Namer { typer: Typer => * (2) If may not derive from itself * (3) The class is not final * (4) If the class is sealed, it is defined in the same compilation unit as the current class + * (unless defined in Java. See JEP-409) * * @param isJava If true, the parent type is in Java mode, and we do not require a stable prefix */ @@ -1566,7 +1567,7 @@ class Namer { typer: Typer => if pclazz.is(Final) then report.error(ExtendFinalClass(cls, pclazz), cls.srcPos) else if pclazz.isEffectivelySealed && pclazz.associatedFile != cls.associatedFile then - if pclazz.is(Sealed) then + if pclazz.is(Sealed) && !pclazz.is(JavaDefined) then report.error(UnableToExtendSealedClass(pclazz), cls.srcPos) else if sourceVersion.isAtLeast(future) then checkFeature(nme.adhocExtensions, diff --git a/compiler/test/dotty/tools/utils.scala b/compiler/test/dotty/tools/utils.scala index 8161631acb44..a8c480088e08 100644 --- a/compiler/test/dotty/tools/utils.scala +++ b/compiler/test/dotty/tools/utils.scala @@ -81,7 +81,14 @@ def toolArgsFor(tool: ToolName, filename: Option[String])(lines: List[String]): // groups are (name, args) // note: ideally we would replace everything that requires this to use directive syntax, however scalajs: --skip has no directive equivalent yet. private val toolArg = raw"(?://|/\*| \*) ?(?i:(${ToolName.values.mkString("|")})):((?:[^*]|\*(?!/))*)".r.unanchored + +// ================================================================================================ +// =================================== VULPIX DIRECTIVES ========================================== +// ================================================================================================ + +/** Directive to specify to vulpix the options to pass to Dotty */ private val directiveOptionsArg = raw"//> using options (.*)".r.unanchored +private val directiveJavacOptions = raw"//> using javacOpt (.*)".r.unanchored // Inspect the lines for compiler options of the form // `//> using options args`, `// scalajs: args`, `/* scalajs: args`, ` * scalajs: args` etc. @@ -90,10 +97,15 @@ private val directiveOptionsArg = raw"//> using options (.*)".r.unanchored def toolArgsParse(lines: List[String], filename: Option[String]): List[(String,String)] = lines.flatMap { case toolArg("scalac", _) => sys.error(s"`// scalac: args` not supported. Please use `//> using options args`${filename.fold("")(f => s" in file $f")}") + case toolArg("javac", _) => sys.error(s"`// javac: args` not supported. Please use `//> using javacOpt args`${filename.fold("")(f => s" in file $f")}") case toolArg(name, args) => List((name, args)) case _ => Nil } ++ - lines.flatMap { case directiveOptionsArg(args) => List(("scalac", args)) case _ => Nil } + lines.flatMap { + case directiveOptionsArg(args) => List(("scalac", args)) + case directiveJavacOptions(args) => List(("javac", args)) + case _ => Nil + } import org.junit.Test import org.junit.Assert._ @@ -104,6 +116,6 @@ class ToolArgsTest: @Test def `tool is present`: Unit = assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: Nil)) @Test def `missing tool is absent`: Unit = assertEquals(Nil, toolArgsFor(ToolName.Javac, None)("// test: -hey" :: Nil)) @Test def `multitool is present`: Unit = - assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: "// javac: -d /tmp" :: Nil)) - assertEquals("-d" :: "/tmp" :: Nil, toolArgsFor(ToolName.Javac, None)("// test: -hey" :: "// javac: -d /tmp" :: Nil)) + assertEquals("-hey" :: Nil, toolArgsFor(ToolName.Test, None)("// test: -hey" :: "// java: -d /tmp" :: Nil)) + assertEquals("-d" :: "/tmp" :: Nil, toolArgsFor(ToolName.Java, None)("// test: -hey" :: "// java: -d /tmp" :: Nil)) end ToolArgsTest diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 74a148d8f734..5b26076f98ab 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -498,6 +498,7 @@ trait ParallelTesting extends RunnerOrchestration { self => case None => true def scalacOptions = toolArgs.getOrElse(ToolName.Scalac, Nil) + def javacOptions = toolArgs.getOrElse(ToolName.Javac, Nil) val flags = flags0 .and(scalacOptions: _*) @@ -512,11 +513,10 @@ trait ParallelTesting extends RunnerOrchestration { self => def compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) { val fullArgs = Array( - "javac", "-encoding", StandardCharsets.UTF_8.name, - ) ++ flags.javacFlags ++ fs + ) ++ flags.javacFlags ++ javacOptions++ fs - val process = Runtime.getRuntime.exec(fullArgs) + val process = Runtime.getRuntime.exec("javac" +: fullArgs) val output = Source.fromInputStream(process.getErrorStream).mkString if waitForJudiciously(process) != 0 then Some(output) diff --git a/tests/neg/i18533.check b/tests/neg/i18533.check new file mode 100644 index 000000000000..0e117edc3195 --- /dev/null +++ b/tests/neg/i18533.check @@ -0,0 +1,8 @@ +-- Error: tests/neg/i18533/Pet_SCALA_ONLY.java:3:10 -------------------------------------------------------------------- +3 |class Pet permits Cat { // error + | ^^^^^^^ + | A type declaration that has a permits clause should have a sealed modifier +-- Error: tests/neg/i18533/non-SCALA_ONLY.java:4:7 --------------------------------------------------------------------- +4 |public non class Test { // error + | ^^^ + | Identifier 'non' is not allowed here diff --git a/tests/neg/i18533/Cat_SCALA_ONLY.java b/tests/neg/i18533/Cat_SCALA_ONLY.java new file mode 100644 index 000000000000..fec994ca2a0a --- /dev/null +++ b/tests/neg/i18533/Cat_SCALA_ONLY.java @@ -0,0 +1,5 @@ +package i18533; + +public final class Cat extends Pet { + +} diff --git a/tests/neg/i18533/Pet_SCALA_ONLY.java b/tests/neg/i18533/Pet_SCALA_ONLY.java new file mode 100644 index 000000000000..c6784ce58999 --- /dev/null +++ b/tests/neg/i18533/Pet_SCALA_ONLY.java @@ -0,0 +1,5 @@ +package i18533; + +class Pet permits Cat { // error + +} diff --git a/tests/neg/i18533/non-SCALA_ONLY.java b/tests/neg/i18533/non-SCALA_ONLY.java new file mode 100644 index 000000000000..be6aef65da13 --- /dev/null +++ b/tests/neg/i18533/non-SCALA_ONLY.java @@ -0,0 +1,6 @@ +package i18533; + +// Special test for the non-sealed trick (See JavaParsers.scala::modifiers) +public non class Test { // error + +} \ No newline at end of file diff --git a/tests/pos/i18533/Cat.java b/tests/pos/i18533/Cat.java new file mode 100644 index 000000000000..33cdd99949bb --- /dev/null +++ b/tests/pos/i18533/Cat.java @@ -0,0 +1,8 @@ +//> using javacOpt --enable-preview --source 17 +//> test: -jvm 17+ + +package i18533; + +public final class Cat extends Pet { + +} \ No newline at end of file diff --git a/tests/pos/i18533/Dog.java b/tests/pos/i18533/Dog.java new file mode 100644 index 000000000000..0b10b9f06c27 --- /dev/null +++ b/tests/pos/i18533/Dog.java @@ -0,0 +1,8 @@ +//> using javacOpt --enable-preview --source 17 +//> test: -jvm 17+ + +package i18533; + +public non-sealed class Dog extends Pet { + +} \ No newline at end of file diff --git a/tests/pos/i18533/Pet.java b/tests/pos/i18533/Pet.java new file mode 100644 index 000000000000..0985aa1f957f --- /dev/null +++ b/tests/pos/i18533/Pet.java @@ -0,0 +1,8 @@ +//> using javacOpt --enable-preview --source 17 +//> test: -jvm 17+ + +package i18533; + +public sealed class Pet permits Cat, Dog { + +} \ No newline at end of file diff --git a/tests/run/t9915/C_1.java b/tests/run/t9915/C_1.java index 4269cf74e058..ec94b1412741 100644 --- a/tests/run/t9915/C_1.java +++ b/tests/run/t9915/C_1.java @@ -1,6 +1,3 @@ -/* - * javac: -encoding UTF-8 - */ public class C_1 { public static final String NULLED = "X\000ABC"; public static final String SUPPED = "𐒈𐒝𐒑𐒛𐒐𐒘𐒕𐒖";