Skip to content

Commit

Permalink
Merge pull request #10105 from som-snytt/issue/12159-java-sealed
Browse files Browse the repository at this point in the history
Support JDK 17 `PermittedSubclasses` in classfiles
  • Loading branch information
som-snytt committed Apr 25, 2023
2 parents 28eef15 + 5849a58 commit ab8feac
Show file tree
Hide file tree
Showing 38 changed files with 349 additions and 76 deletions.
6 changes: 2 additions & 4 deletions src/compiler/scala/tools/nsc/Global.scala
Expand Up @@ -1727,10 +1727,8 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
} // class Run

def printAllUnits(): Unit = {
print("[[syntax trees at end of %25s]]".format(phase))
exitingPhase(phase)(currentRun.units foreach { unit =>
nodePrinters showUnit unit
})
print(f"[[syntax trees at end of $phase%25s]]")
exitingPhase(phase)(currentRun.units.foreach(nodePrinters.showUnit(_)))
}

/** We resolve the class/object ambiguity by passing a type/term name.
Expand Down
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
58 changes: 48 additions & 10 deletions src/compiler/scala/tools/nsc/javac/JavaParsers.scala
Expand Up @@ -16,14 +16,14 @@
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
import scala.util.chaining._

trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
val global : Global
Expand Down Expand Up @@ -493,11 +493,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 +830,13 @@ 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,9 +850,11 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
javaLangObject()
}
val interfaces = interfacesOpt()
val permits = permitsOpt()
val (statics, body) = typeBody(CLASS)
addCompanionObject(statics, atPos(pos) {
ClassDef(mods, name, tparams, makeTemplate(superclass :: interfaces, body))
.tap(cd => if (permits.nonEmpty) cd.updateAttachment(PermittedSubclasses(permits)))
})
}

Expand Down Expand Up @@ -878,11 +915,13 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
} else {
List(javaLangObject())
}
val permits = permitsOpt()
val (statics, body) = typeBody(INTERFACE)
addCompanionObject(statics, atPos(pos) {
ClassDef(mods | Flags.TRAIT | Flags.INTERFACE | Flags.ABSTRACT,
name, tparams,
makeTemplate(parents, body))
.tap(cd => if (permits.nonEmpty) cd.updateAttachment(PermittedSubclasses(permits)))
})
}

Expand All @@ -905,7 +944,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 @@ -853,10 +853,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 @@ -905,6 +906,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 @@ -1357,6 +1369,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 @@ -1365,6 +1378,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 @@ -1494,7 +1510,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
}
}
}
}
Expand Up @@ -885,7 +885,7 @@ trait ContextErrors extends splain.SplainErrors {

// def stabilize
def NotAValueError(tree: Tree, sym: Symbol) = {
issueNormalTypeError(tree, sym.kindString + " " + sym.fullName + " is not a value")
issueNormalTypeError(tree, s"${sym.kindString} ${sym.fullName} is not a value")
setError(tree)
}

Expand Down
72 changes: 41 additions & 31 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Expand Up @@ -427,29 +427,27 @@ trait Namers extends MethodSynthesis {
/** Given a ClassDef or ModuleDef, verifies there isn't a companion which
* has been defined in a separate file.
*/
@nowarn("cat=lint-nonlocal-return")
def validateCompanionDefs(tree: ImplDef): Unit = {
val sym = tree.symbol orElse { return }
val ctx = if (context.owner.isPackageObjectClass) context.outer else context
val module = if (sym.isModule) sym else ctx.scope lookupModule tree.name
val clazz = if (sym.isClass) sym else ctx.scope lookupClass tree.name
val fails = (
module.isModule
&& clazz.isClass
&& !module.isSynthetic
&& !clazz.isSynthetic
&& (clazz.sourceFile ne null)
&& (module.sourceFile ne null)
&& !(module isCoDefinedWith clazz)
&& module.exists
&& clazz.exists
&& (currentRun.compiles(clazz) == currentRun.compiles(module))
)
if (fails) {
reporter.error(tree.pos, (
s"Companions '$clazz' and '$module' must be defined in same file:\n"
+ s" Found in ${clazz.sourceFile.canonicalPath} and ${module.sourceFile.canonicalPath}")
val sym = tree.symbol
if (sym != NoSymbol) {
val ctx = if (context.owner.isPackageObjectClass) context.outer else context
val module = if (sym.isModule) sym else ctx.scope.lookupModule(tree.name)
val clazz = if (sym.isClass) sym else ctx.scope.lookupClass(tree.name)
val fails = (
module.isModule
&& clazz.isClass
&& !module.isSynthetic
&& !clazz.isSynthetic
&& (clazz.sourceFile ne null)
&& (module.sourceFile ne null)
&& !module.isCoDefinedWith(clazz)
&& module.exists
&& clazz.exists
&& currentRun.compiles(clazz) == currentRun.compiles(module)
)
if (fails) reporter.error(tree.pos,
sm"""|Companions '$clazz' and '$module' must be defined in same file:
| Found in ${clazz.sourceFile.canonicalPath} and ${module.sourceFile.canonicalPath}""")
}
}

Expand Down Expand Up @@ -1186,17 +1184,25 @@ trait Namers extends MethodSynthesis {
val pending = mutable.ListBuffer[AbsTypeError]()
parentTrees foreach { tpt =>
val ptpe = tpt.tpe
if (!ptpe.isError) {
if (!ptpe.isError && !phase.erasedTypes) {
val psym = ptpe.typeSymbol
val sameSourceFile = context.unit.source.file == psym.sourceFile

if (psym.isSealed && !phase.erasedTypes)
if (sameSourceFile)
psym addChild context.owner
if (psym.isSealed) {
val sameSourceFile = context.unit.source.file == psym.sourceFile
val okChild =
if (psym.isJava)
psym.attachments.get[PermittedSubclassSymbols] match {
case Some(permitted) => permitted.permits.exists(_ == clazz)
case _ => sameSourceFile
}
else
sameSourceFile
if (okChild)
psym.addChild(clazz)
else
pending += ParentSealedInheritanceError(tpt, psym)
if (psym.isLocalToBlock && psym.isClass && !phase.erasedTypes)
psym addChild context.owner
}
if (psym.isLocalToBlock && psym.isClass)
psym.addChild(clazz)
}
}
pending.foreach(ErrorUtils.issueTypeError)
Expand All @@ -1214,13 +1220,12 @@ trait Namers extends MethodSynthesis {

// add apply and unapply methods to companion objects of case classes,
// unless they exist already; here, "clazz" is the module class
if (clazz.isModuleClass) {
if (clazz.isModuleClass)
clazz.attachments.get[ClassForCaseCompanionAttachment] foreach { cma =>
val cdef = cma.caseClass
assert(cdef.mods.isCase, "expected case class: "+ cdef)
addApplyUnapply(cdef, templateNamer)
}
}

// add the copy method to case classes; this needs to be done here, not in SyntheticMethods, because
// the namer phase must traverse this copy method to create default getters for its parameters.
Expand Down Expand Up @@ -1266,6 +1271,11 @@ trait Namers extends MethodSynthesis {
val res = GenPolyType(tparams0, resultType)

val pluginsTp = pluginsTypeSig(res, typer, cdef, WildcardType)
cdef.getAndRemoveAttachment[PermittedSubclasses].foreach { permitted =>
clazz.updateAttachment[PermittedSubclassSymbols] {
PermittedSubclassSymbols(permitted.permits.map(typer.typed(_, Mode.NOmode).symbol))
}
}

// Already assign the type to the class symbol (monoTypeCompleter will do it again).
// Allows isDerivedValueClass to look at the info.
Expand Down
13 changes: 6 additions & 7 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -1811,7 +1811,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (parent == DynamicClass) checkFeature(parentPos, currentRun.runDefinitions.DynamicsFeature)

def validateParentClass(parent: Tree, superclazz: Symbol) =
if (!parent.isErrorTyped) {
if (!parent.isErrorTyped) { // redundant
val psym = parent.tpe.typeSymbol.initialize

if (!context.unit.isJava)
Expand Down Expand Up @@ -1876,7 +1876,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper

if (!parents.isEmpty && parents.forall(!_.isErrorTyped)) {
val superclazz = parents.head.tpe.typeSymbol
for (p <- parents) validateParentClass(p, superclazz)
parents.foreach(validateParentClass(_, superclazz))
}

pending.foreach(ErrorUtils.issueTypeError)
Expand Down Expand Up @@ -2054,7 +2054,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val body1 = pluginsEnterStats(this, namer.expandMacroAnnotations(templ.body))
enterSyms(context.outer.make(templ, clazz, clazz.info.decls), body1)
if (!templ.isErrorTyped) // if `parentTypes` has invalidated the template, don't validate it anymore
validateParentClasses(parents1, selfType, clazz.isTrait)
validateParentClasses(parents1, selfType, clazz.isTrait)
if (clazz.isCase)
validateNoCaseAncestor(clazz)
if (clazz.isTrait && hasSuperArgs(parents1.head))
Expand Down Expand Up @@ -2088,11 +2088,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
ctors.foreach(AuxConstrInConstantAnnotation(_, clazz))
}


if (clazz.isTrait) {
for (decl <- clazz.info.decls if decl.isTerm && decl.isEarlyInitialized) {
context.warning(decl.pos, "Implementation restriction: early definitions in traits are not initialized before the super class is initialized.", WarningCategory.Other)
}
for (decl <- clazz.info.decls)
if (decl.isTerm && decl.isEarlyInitialized)
context.warning(decl.pos, "Implementation restriction: early definitions in traits are not initialized before the super class is initialized.", WarningCategory.Other)
}

treeCopy.Template(templ, parents1, self1, body3) setType clazz.tpe_*
Expand Down
2 changes: 1 addition & 1 deletion src/partest/scala/tools/partest/nest/AbstractRunner.scala
Expand Up @@ -291,7 +291,7 @@ class AbstractRunner(val config: RunnerSpec.Config, protected final val testSour
List(pathSettings.testParent / norm)
}
}
.distinct
.distinct.filter(denotesTestPath)
}

val isRerun = config.optFailed
Expand Down
4 changes: 4 additions & 0 deletions src/reflect/scala/reflect/internal/StdAttachments.scala
Expand Up @@ -154,4 +154,8 @@ trait StdAttachments {
case object FieldTypeInferred extends PlainAttachment

case class LookupAmbiguityWarning(msg: String) extends PlainAttachment

/** Java sealed classes may be qualified with a permits clause specifying allowed subclasses. */
case class PermittedSubclasses(permits: List[Tree]) extends PlainAttachment
case class PermittedSubclassSymbols(permits: List[Symbol]) extends PlainAttachment
}

0 comments on commit ab8feac

Please sign in to comment.