diff --git a/src/compiler/scala/tools/nsc/ast/parser/CommonTokens.scala b/src/compiler/scala/tools/nsc/ast/parser/CommonTokens.scala index 090c517054f7..a3b858c34fbc 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/CommonTokens.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/CommonTokens.scala @@ -51,7 +51,7 @@ abstract class CommonTokens { // J: PUBLIC = 42 final val PROTECTED = 43 final val PRIVATE = 44 - // S: SEALED = 45 + final val SEALED = 45 // J: contextual keyword final val ABSTRACT = 46 // J: DEFAULT = 47 // J: STATIC = 48 diff --git a/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala b/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala index 56dbf3db7494..c846cc55ec81 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Tokens.scala @@ -28,7 +28,6 @@ object Tokens extends CommonTokens { /** modifiers */ final val IMPLICIT = 40 final val OVERRIDE = 41 - final val SEALED = 45 final val LAZY = 55 final val MACRO = 57 diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index 2049693a81f3..76660ea59e6d 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -16,13 +16,12 @@ package scala.tools.nsc package javac -import scala.collection.mutable.ListBuffer import symtab.Flags import JavaTokens._ -import scala.annotation.tailrec +import scala.annotation._ +import scala.collection.mutable.ListBuffer import scala.language.implicitConversions -import scala.reflect.internal.util.Position -import scala.reflect.internal.util.ListOfNil +import scala.reflect.internal.util.{ListOfNil, Position} import scala.tools.nsc.Reporting.WarningCategory trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { @@ -493,11 +492,39 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { case SYNCHRONIZED => in.nextToken() case _ => - val privateWithin: TypeName = - if (isPackageAccess && !inInterface) thisPackageName - else tpnme.EMPTY - - return Modifiers(flags, privateWithin) withAnnotations annots + val unsealed = 0L // no flag for UNSEALED + def consume(added: FlagSet): false = { in.nextToken(); flags |= added; false } + def lookingAhead(s: String): Boolean = { + import scala.reflect.internal.Chars._ + var i = 0 + val n = s.length + val lookahead = in.in.lookahead + while (i < n && lookahead.ch != SU) { + if (lookahead.ch != s.charAt(i)) return false + lookahead.next() + i += 1 + } + i == n && Character.isWhitespace(lookahead.ch) + } + val done = (in.token != IDENTIFIER) || ( + in.name match { + case nme.javaRestrictedIdentifiers.SEALED => consume(Flags.SEALED) + case nme.javaRestrictedIdentifiers.UNSEALED => consume(unsealed) + case nme.javaRestrictedIdentifiers.NON => + !lookingAhead("-sealed") || { + in.nextToken() + in.nextToken() + consume(unsealed) + } + case _ => true + } + ) + if (done) { + val privateWithin: TypeName = + if (isPackageAccess && !inInterface) thisPackageName + else tpnme.EMPTY + return Modifiers(flags, privateWithin) withAnnotations annots + } } } abort("should not be here") @@ -802,6 +829,14 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { List() } + def permitsOpt() = + if (in.token == IDENTIFIER && in.name == nme.javaRestrictedIdentifiers.PERMITS) { + in.nextToken() + repsep(() => typ(), COMMA) + } else { + Nil + } + def classDecl(mods: Modifiers): List[Tree] = { accept(CLASS) val pos = in.currentPos @@ -815,6 +850,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { javaLangObject() } val interfaces = interfacesOpt() + @unused val permits = permitsOpt() val (statics, body) = typeBody(CLASS) addCompanionObject(statics, atPos(pos) { ClassDef(mods, name, tparams, makeTemplate(superclass :: interfaces, body)) @@ -878,6 +914,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { } else { List(javaLangObject()) } + @unused val permits = permitsOpt() val (statics, body) = typeBody(INTERFACE) addCompanionObject(statics, atPos(pos) { ClassDef(mods | Flags.TRAIT | Flags.INTERFACE | Flags.ABSTRACT, @@ -905,7 +942,6 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { } else if (in.token == SEMI) { in.nextToken() } else { - // See "14.3. Local Class and Interface Declarations" adaptRecordIdentifier() if (in.token == ENUM || in.token == RECORD || definesInterface(in.token)) diff --git a/src/compiler/scala/tools/nsc/javac/JavaTokens.scala b/src/compiler/scala/tools/nsc/javac/JavaTokens.scala index a124d1b90aaa..66fcdf7c069c 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaTokens.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaTokens.scala @@ -38,6 +38,7 @@ object JavaTokens extends ast.parser.CommonTokens { final val NATIVE = 53 final val STRICTFP = 54 final val THROWS = 56 + final val UNSEALED = 57 // contextual keyword /** templates */ final val INTERFACE = 66 diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index ac20d4f16e0d..fa9eeaf2233e 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -904,6 +904,11 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) { } in.skip(attrLen) + case tpnme.PermittedSubclassesATTR => + debuglog(s"$sym is Java sealed.") + sym.setFlag(SEALED) + in.skip(attrLen) // https://openjdk.java.net/jeps/360 but ignore permits and respect only Scala semantics + case _ => in.skip(attrLen) } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index c41d05c658dd..402cd017495e 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -320,6 +320,7 @@ trait StdNames { final val SignatureATTR: NameType = nameType("Signature") final val SourceFileATTR: NameType = nameType("SourceFile") final val SyntheticATTR: NameType = nameType("Synthetic") + final val PermittedSubclassesATTR: NameType = nameType("PermittedSubclasses") final val scala_ : NameType = nameType("scala") @@ -1281,7 +1282,11 @@ trait StdNames { // A type identifier is an identifier that is not the character sequence var, yield, or record. // An unqualified method identifier is an identifier that is not the character sequence yield. class JavaRestrictedIdentifiers { + final val PERMITS: TermName = TermName("permits") final val RECORD: TermName = TermName("record") + final val SEALED: TermName = TermName("sealed") + final val UNSEALED: TermName = TermName("non-sealed") + final val NON: TermName = TermName("non") final val VAR: TermName = TermName("var") final val YIELD: TermName = TermName("yield") } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 5a07f3ccd75c..105354361005 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1426,7 +1426,7 @@ trait Trees extends api.Trees { else Modifiers(flags, privateWithin, newAnns) setPositions positions } - override def toString = "Modifiers(%s, %s, %s)".format(flagString, annotations mkString ", ", positions) + override def toString = s"Modifiers($flagString, ${annotations.mkString(",")}, $positions)" } object Modifiers extends ModifiersExtractor diff --git a/test/files/neg/t12159.check b/test/files/neg/t12159.check new file mode 100644 index 000000000000..bda2e48622ce --- /dev/null +++ b/test/files/neg/t12159.check @@ -0,0 +1,7 @@ +s.scala:5: error: illegal inheritance from sealed class H +class S extends H { + ^ +s.scala:8: error: illegal inheritance from sealed trait I +trait T extends I { + ^ +2 errors diff --git a/test/files/neg/t12159/H.java b/test/files/neg/t12159/H.java new file mode 100644 index 000000000000..3a15309f733e --- /dev/null +++ b/test/files/neg/t12159/H.java @@ -0,0 +1,19 @@ +// javaVersion: 17+ +package p; + +sealed public class H { +} + +final class K extends H { +} + +non-sealed class L extends H { +} + +sealed +class P extends H { +} + +final +class Q extends P { +} diff --git a/test/files/neg/t12159/I.java b/test/files/neg/t12159/I.java new file mode 100644 index 000000000000..f91c69dd7828 --- /dev/null +++ b/test/files/neg/t12159/I.java @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p; + +sealed interface I permits J { +} diff --git a/test/files/neg/t12159/J.java b/test/files/neg/t12159/J.java new file mode 100644 index 000000000000..5bd2c4c92374 --- /dev/null +++ b/test/files/neg/t12159/J.java @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p; + +sealed public class J implements I permits M { +} diff --git a/test/files/neg/t12159/M.java b/test/files/neg/t12159/M.java new file mode 100644 index 000000000000..245c79304d29 --- /dev/null +++ b/test/files/neg/t12159/M.java @@ -0,0 +1,9 @@ +// javaVersion: 17+ + +package p; + +public final class M extends J { +} + +final class N extends L { +} diff --git a/test/files/neg/t12159/s.scala b/test/files/neg/t12159/s.scala new file mode 100644 index 000000000000..b8ec52070d07 --- /dev/null +++ b/test/files/neg/t12159/s.scala @@ -0,0 +1,9 @@ +// javaVersion: 17+ + +package p + +class S extends H { +} + +trait T extends I { +} diff --git a/test/files/neg/t12159b.check b/test/files/neg/t12159b.check new file mode 100644 index 000000000000..328a4610bcaa --- /dev/null +++ b/test/files/neg/t12159b.check @@ -0,0 +1,4 @@ +s_2.scala:6: error: illegal inheritance from sealed trait I_1 +class S extends I_1 + ^ +1 error diff --git a/test/files/neg/t12159b/I_1.java b/test/files/neg/t12159b/I_1.java new file mode 100644 index 000000000000..377dd7f3e4f1 --- /dev/null +++ b/test/files/neg/t12159b/I_1.java @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p; + +sealed interface I_1 permits J_1 { +} diff --git a/test/files/neg/t12159b/J_1.java b/test/files/neg/t12159b/J_1.java new file mode 100644 index 000000000000..e0d71ca895ad --- /dev/null +++ b/test/files/neg/t12159b/J_1.java @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p; + +public final class J_1 implements I_1 { +} diff --git a/test/files/neg/t12159b/s_2.scala b/test/files/neg/t12159b/s_2.scala new file mode 100644 index 000000000000..33c2452ecc64 --- /dev/null +++ b/test/files/neg/t12159b/s_2.scala @@ -0,0 +1,8 @@ +// javaVersion: 17+ +// skalac: -Vdebug -Vlog:_ + +package p + +class S extends I_1 + +//[log typer] trait I_1 is Java sealed. diff --git a/test/files/pos/t12159/H.java b/test/files/pos/t12159/H.java new file mode 100644 index 000000000000..3a15309f733e --- /dev/null +++ b/test/files/pos/t12159/H.java @@ -0,0 +1,19 @@ +// javaVersion: 17+ +package p; + +sealed public class H { +} + +final class K extends H { +} + +non-sealed class L extends H { +} + +sealed +class P extends H { +} + +final +class Q extends P { +} diff --git a/test/files/pos/t12159/I.java b/test/files/pos/t12159/I.java new file mode 100644 index 000000000000..f91c69dd7828 --- /dev/null +++ b/test/files/pos/t12159/I.java @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p; + +sealed interface I permits J { +} diff --git a/test/files/pos/t12159/J.java b/test/files/pos/t12159/J.java new file mode 100644 index 000000000000..5bd2c4c92374 --- /dev/null +++ b/test/files/pos/t12159/J.java @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p; + +sealed public class J implements I permits M { +} diff --git a/test/files/pos/t12159/M.java b/test/files/pos/t12159/M.java new file mode 100644 index 000000000000..245c79304d29 --- /dev/null +++ b/test/files/pos/t12159/M.java @@ -0,0 +1,9 @@ +// javaVersion: 17+ + +package p; + +public final class M extends J { +} + +final class N extends L { +} diff --git a/test/files/pos/t12159/s.scala b/test/files/pos/t12159/s.scala new file mode 100644 index 000000000000..29eb9518ea6c --- /dev/null +++ b/test/files/pos/t12159/s.scala @@ -0,0 +1,6 @@ +// javaVersion: 17+ + +package p + +class S extends L { +}