Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JDK 17 PermittedSubclasses in classfiles #10105

Merged
merged 1 commit into from Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,
lrytz marked this conversation as resolved.
Show resolved Hide resolved
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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classfiles are allowed to name spuriously permitted subclasses (that aren't subclasses). IIRC.

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
}