Skip to content

Commit

Permalink
Accept (non-)sealed in Java
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Oct 31, 2022
1 parent e86724e commit 20e7535
Show file tree
Hide file tree
Showing 32 changed files with 296 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/ast/parser/CommonTokens.scala
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/compiler/scala/tools/nsc/ast/parser/Tokens.scala
Expand Up @@ -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

Expand Down
56 changes: 46 additions & 10 deletions src/compiler/scala/tools/nsc/javac/JavaParsers.scala
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/javac/JavaTokens.scala
Expand Up @@ -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
Expand Down
Expand Up @@ -852,10 +852,11 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
in.skip(attrLen)

case tpnme.RuntimeAnnotationATTR =>
val numAnnots = u2()
val numAnnots = u2()
val annots = new ListBuffer[AnnotationInfo]
for (n <- 0 until numAnnots; annot <- parseAnnotation(u2()))
annots += annot
numAnnots times {
parseAnnotation(u2()).foreach(annots.addOne)
}
/* `sym.withAnnotations(annots)`, like `sym.addAnnotation(annot)`, prepends,
* so if we parsed in classfile order we would wind up with the annotations
* in reverse order in `sym.annotations`. Instead we just read them out the
Expand Down Expand Up @@ -904,6 +905,17 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
}
in.skip(attrLen)

case tpnme.PermittedSubclassesATTR =>
sym.setFlag(SEALED)
val numberOfClasses = u2()
numberOfClasses times {
val k = pool.getClassSymbol(u2())
completer match {
case ctc: ClassTypeCompleter => ctc.permittedSubclasses ::= k // sym.addChild(k)
case _ =>
}
}

case _ =>
in.skip(attrLen)
}
Expand Down Expand Up @@ -1356,6 +1368,7 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
var exceptions: List[NameOrString] = Nil
}
private final class ClassTypeCompleter(name: Name, jflags: JavaAccFlags, parent: NameOrString, ifaces: List[NameOrString]) extends JavaTypeCompleter {
var permittedSubclasses: List[symbolTable.Symbol] = Nil
override def complete(sym: symbolTable.Symbol): Unit = {
val info = if (sig != null) sigToType(sym, sig) else {
val superTpe = if (parent == null) definitions.AnyClass.tpe_* else getClassSymbol(parent.value).tpe_*
Expand All @@ -1364,6 +1377,9 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
ClassInfoType(superTpe1 :: ifacesTypes, instanceScope, clazz)
}
sym.setInfo(info)
for (k <- permittedSubclasses)
if (k.parentSymbols.contains(sym))
sym.addChild(k)
}
}

Expand Down Expand Up @@ -1493,7 +1509,13 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
if (flags.isStatic) staticScope else instanceScope
}
object ClassfileParser {
private implicit class GoodTimes(val n: Int) extends AnyVal {
def times(body: => Unit) = (1 to n).foreach(_ => body)
private implicit class GoodTimes(private val n: Int) extends AnyVal {
def times(body: => Unit): Unit = {
var i = n
while (i > 0) {
body
i -= 1
}
}
}
}
4 changes: 2 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -1192,11 +1192,11 @@ trait Namers extends MethodSynthesis {

if (psym.isSealed && !phase.erasedTypes)
if (sameSourceFile)
psym addChild context.owner
psym.addChild(clazz)
else
pending += ParentSealedInheritanceError(tpt, psym)
if (psym.isLocalToBlock && psym.isClass && !phase.erasedTypes)
psym addChild context.owner
psym.addChild(clazz)
}
}
pending.foreach(ErrorUtils.issueTypeError)
Expand Down
12 changes: 9 additions & 3 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -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")

Expand Down Expand Up @@ -1277,11 +1278,16 @@ trait StdNames {
final val keywords = kw.result
}

// "The identifiers var, yield, and record are restricted identifiers because they are not allowed in some contexts"
// 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.
// The identifiers non-sealed, permits, record, sealed, var, and yield are restricted identifiers
// because they are not allowed in some contexts.
// A type identifier is an identifier that is not the character sequence permits, record, sealed, var, or yield.
// An unqualified method identifier is an identifier that is not the character sequence yield. (JLS 3.8)
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")
}
Expand Down
17 changes: 8 additions & 9 deletions src/reflect/scala/reflect/internal/Symbols.scala
Expand Up @@ -1011,14 +1011,6 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
final def isStaticOwner: Boolean =
isPackageClass || isModuleClass && isStatic

/** A helper function for isEffectivelyFinal. */
private def isNotOverridden = (
owner.isClass && (
owner.isEffectivelyFinal
|| (owner.isSealed && owner.sealedChildren.forall(c => c.isEffectivelyFinal && (overridingSymbol(c) == NoSymbol)))
)
)

/** Is this symbol effectively final? I.e, it cannot be overridden */
final def isEffectivelyFinal: Boolean = (
((this hasFlag FINAL | PACKAGE) && this != SingletonClass)
Expand All @@ -1029,7 +1021,14 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
|| isClass && !isRefinementClass && originalOwner.isTerm && children.isEmpty
)
/** Is this symbol effectively final or a concrete term member of sealed class whose children do not override it */
final def isEffectivelyFinalOrNotOverridden: Boolean = isEffectivelyFinal || (isTerm && !isDeferred && isNotOverridden)
final def isEffectivelyFinalOrNotOverridden: Boolean = {
def isNotOverridden =
owner.isClass && (
owner.isEffectivelyFinal
|| owner.isSealed && owner.sealedChildren.forall(c => c.isEffectivelyFinal && overridingSymbol(c) == NoSymbol)
)
isEffectivelyFinal || isTerm && !isDeferred && isNotOverridden
}

/** Is this symbol owned by a package? */
final def isTopLevel = owner.isPackageClass
Expand Down
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/Trees.scala
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions src/reflect/scala/reflect/internal/Types.scala
Expand Up @@ -1769,9 +1769,9 @@ trait Types
}
}
}
//Console.println("baseTypeSeq(" + typeSymbol + ") = " + baseTypeSeqCache.toList);//DEBUG
//Console.println(s"baseTypeSeq(${tpe.typeSymbol}) = ${tpe.baseTypeSeqCache.toList}") //DEBUG
if (tpe.baseTypeSeqCache eq undetBaseTypeSeq)
throw new TypeError("illegal cyclic inheritance involving " + tpe.typeSymbol)
throw new TypeError(s"illegal cyclic inheritance involving ${tpe.typeSymbol}")
}

protected def defineBaseClassesOfCompoundType(tpe: CompoundType): Unit = {
Expand Down Expand Up @@ -2792,8 +2792,9 @@ trait Types
}
}
}
//Console.println(s"baseTypeSeq(${tpe.typeSymbol}) = ${tpe.baseTypeSeqCache.toList}") //DEBUG
if (tpe.baseTypeSeqCache == undetBaseTypeSeq)
throw new TypeError("illegal cyclic inheritance involving " + tpe.sym)
throw new TypeError(s"illegal cyclic inheritance involving ${tpe.sym}")
}

/** A class representing a method type with parameters.
Expand Down
7 changes: 7 additions & 0 deletions 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
19 changes: 19 additions & 0 deletions 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 {
}
6 changes: 6 additions & 0 deletions test/files/neg/t12159/I.java
@@ -0,0 +1,6 @@
// javaVersion: 17+

package p;

sealed interface I permits J {
}
6 changes: 6 additions & 0 deletions test/files/neg/t12159/J.java
@@ -0,0 +1,6 @@
// javaVersion: 17+

package p;

sealed public class J implements I permits M {
}
9 changes: 9 additions & 0 deletions 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 {
}
9 changes: 9 additions & 0 deletions test/files/neg/t12159/s.scala
@@ -0,0 +1,9 @@
// javaVersion: 17+

package p

class S extends H {
}

trait T extends I {
}
4 changes: 4 additions & 0 deletions 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
6 changes: 6 additions & 0 deletions test/files/neg/t12159b/I_1.java
@@ -0,0 +1,6 @@
// javaVersion: 17+

package p;

sealed interface I_1 permits J_1 {
}
6 changes: 6 additions & 0 deletions test/files/neg/t12159b/J_1.java
@@ -0,0 +1,6 @@
// javaVersion: 17+

package p;

public final class J_1 implements I_1 {
}
8 changes: 8 additions & 0 deletions 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.
7 changes: 7 additions & 0 deletions test/files/neg/t12159c.check
@@ -0,0 +1,7 @@
s_2.scala:7: warning: match may not be exhaustive.
It would fail on the following input: K()
h match {
^
error: No warnings can be incurred under -Werror.
1 warning
1 error

0 comments on commit 20e7535

Please sign in to comment.