From aaaf8e1d69e9c45e2a9872f2b7e4bc99afcbc499 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 22 Apr 2024 22:47:27 -0700 Subject: [PATCH] Companions and nested classes are nestmates --- src/compiler/scala/tools/nsc/Global.scala | 1 + .../nsc/backend/jvm/BCodeBodyBuilder.scala | 7 +- .../nsc/backend/jvm/BCodeSkelBuilder.scala | 16 ++++- .../tools/nsc/backend/jvm/GenBCode.scala | 8 +-- .../tools/nsc/transform/ExplicitOuter.scala | 9 ++- .../scala/tools/nsc/transform/Flatten.scala | 35 ++++++++++ .../reflect/internal/StdAttachments.scala | 6 ++ .../scala/reflect/internal/Symbols.scala | 5 +- .../reflect/runtime/JavaUniverseForce.scala | 2 + test/files/run/t6882.scala | 68 +++++++++++++++++++ 10 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 test/files/run/t6882.scala diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index e816ea58f1b1..817a27322fc4 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1173,6 +1173,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) keepPhaseStack = settings.log.isSetByUser val isScala3: Boolean = settings.isScala3: @nowarn + val isJDK11: Boolean = settings.targetValue.toInt >= 11 object sourceFeatures { private val s = settings diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index d6d858e85840..a8fd0727d909 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -711,13 +711,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { generatedType = genPrimitiveOp(app, expectedType) } else { // normal method call def isTraitSuperAccessorBodyCall = app.hasAttachment[UseInvokeSpecial.type] + def isPrivateSpecial = sym.isPrivate && (claszSymbol.info <:< sym.owner.info) val invokeStyle = if (sym.isStaticMember) InvokeStyle.Static - else if (sym.isPrivate || sym.isClassConstructor) InvokeStyle.Special - else if (isTraitSuperAccessorBodyCall) + else if (isPrivateSpecial || sym.isClassConstructor || isTraitSuperAccessorBodyCall) InvokeStyle.Special - else InvokeStyle.Virtual + else + InvokeStyle.Virtual if (invokeStyle.hasInstance) genLoadQualifier(fun) genLoadArguments(args, paramTKs(app)) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index b169e0e9d645..852d3fd14855 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -55,7 +55,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { * * Given that CleanUp delivers trees that produce values on the stack, * the entry-point to all-things instruction-emit is `genLoad()`. - * There, an operation taking N arguments results in recursively emitting instructions to lead each of them, + * There, an operation taking N arguments results in recursively emitting instructions to load each of them, * followed by emitting instructions to process those arguments (to be found at run-time on the operand-stack). * * In a few cases the above recipe deserves more details, as provided in the documentation for: @@ -124,7 +124,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { thisBTypeDescriptor = thisBType.descriptor cnode = new ClassNode1() - initJClass(cnode) + initJClass() val cd = if (isCZStaticModule) { // Move statements from the primary constructor following the superclass constructor call to // a newly synthesised tree representing the "", which also assigns the MODULE$ field. @@ -182,7 +182,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { /* * must-single-thread */ - private def initJClass(@unused jclass: asm.ClassVisitor): Unit = { + private def initJClass(): Unit = { val bType = classBTypeFromSymbol(claszSymbol) val superClass = bType.info.get.superClass.getOrElse(ObjectRef).internalName @@ -199,6 +199,16 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { cnode.visitSource(cunit.source.toString, null /* SourceDebugExtension */) } + if (currentRun.isJDK11) + claszSymbol.attachments.get[NestHost] match { + case Some(NestHost(host)) => cnode.visitNestHost(internalName(host)) + case None => + claszSymbol.attachments.get[NestMembers] match { + case Some(NestMembers(members)) => for (m <- members) cnode.visitNestMember(internalName(m)) + case None => + } + } + enclosingMethodAttribute(claszSymbol, internalName, methodBTypeFromSymbol(_).descriptor) match { case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => cnode.visitOuterClass(className, methodName, methodDescriptor) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 62e8c75c106a..79b3dbe4989c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -97,12 +97,8 @@ abstract class GenBCode extends SubComponent { generatedClassHandler = GeneratedClassHandler(global) statistics.stopTimer(statistics.bcodeInitTimer, initStart) } - def writeOtherFiles(): Unit = { - global.plugins foreach { - plugin => - plugin.writeAdditionalOutputs(postProcessor.classfileWriter) - } - } + + def writeOtherFiles(): Unit = global.plugins.foreach(_.writeAdditionalOutputs(postProcessor.classfileWriter)) private def close(): Unit = List[AutoCloseable]( diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 700bcf275d60..6722daa6ee58 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -434,6 +434,13 @@ abstract class ExplicitOuter extends InfoTransform else atPos(tree.pos)(outerPath(outerValue, currentClass.outerClass, sym)) // (5) case Select(qual, name) => + // c0 belongs to same "nest" as c1 if c0 is enclosed by c1, + // or more generally their top enclosing classes are either identical or companions + def isNestable(c0: Symbol, c1: Symbol): Boolean = currentRun.isJDK11 && { + val top0 = c0.enclosingTopLevelClass + val top1 = c1.enclosingTopLevelClass + top0 == top1 || top0.linkedClassOfClass == top1 + } // make not private symbol accessed from inner classes, as well as // symbols accessed from @inline methods // @@ -442,7 +449,7 @@ abstract class ExplicitOuter extends InfoTransform def enclMethodIsInline = closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass // scala/bug#8710 The extension method condition reflects our knowledge that a call to `new Meter(12).privateMethod` // with later be rewritten (in erasure) to `Meter.privateMethod$extension(12)`. - if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension) + if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension && !isNestable(currentClass, sym.owner)) sym.makeNotPrivate(sym.owner) val qsym = qual.tpe.widen.typeSymbol diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala index 01cf08418f98..9acf73a77112 100644 --- a/src/compiler/scala/tools/nsc/transform/Flatten.scala +++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala @@ -146,6 +146,21 @@ abstract class Flatten extends InfoTransform { if (tree.symbol.sourceModule.isStaticModule) removeSymbolInCurrentScope(tree.symbol.sourceModule) EmptyTree + case ClassDef(_, _, _, _) if !tree.symbol.isModuleClass => // !tree.symbol.isNestedClass + val sym = tree.symbol + val module = sym.companion.moduleClass + if (module.exists) { + module.attachments.get[NestHost] match { + case Some(NestHost(host)) => assert(host == sym, s"bad $module host $host != $sym") + case None => module.updateAttachment(NestHost(sym)) + } + val members = sym.attachments.get[NestMembers] match { + case Some(NestMembers(members)) => module :: members + case None => module :: Nil + } + sym.updateAttachment(NestMembers(members)) + } + tree.transform(this) case _ => tree.transform(this) } @@ -173,6 +188,26 @@ abstract class Flatten extends InfoTransform { val stats1 = super.transformStats(stats, exprOwner) if (currentOwner.isPackageClass) { val lifted = liftedDefs.remove(currentOwner).toList.flatten + for (d <- lifted) { + val sym = d.symbol + val top = { + val tlc = sym.originalEnclosingTopLevelClassOrDummy + if (tlc.isModuleClass) { + val cmp = tlc.linkedClassOfClass + if (cmp.exists) cmp else tlc + } + else tlc + } + sym.attachments.get[NestHost] match { + case Some(NestHost(host)) => assert(host == top, s"bad $sym host $host != $top") + case None => sym.updateAttachment(NestHost(top)) + } + val members = top.attachments.get[NestMembers] match { + case Some(NestMembers(members)) => sym :: members + case None => sym :: Nil + } + top.updateAttachment(NestMembers(members)) + } stats1 ::: lifted } else stats1 diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index fe9f22663010..2b45e3ff3c0b 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -186,4 +186,10 @@ trait StdAttachments { /** Force desugaring Match trees, don't emit switches. Attach to DefDef trees or their symbol. */ case object ForceMatchDesugar extends PlainAttachment + + /** A top-level class may be a "nest host" with given "nest members" (nested classes or companion). */ + case class NestMembers(members: List[Symbol]) extends PlainAttachment + + /** A nested class or a top-level companion module may have a "nest host" for which it is a "nest member". */ + case class NestHost(host: Symbol) extends PlainAttachment } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 57548f7b3ed5..6873e92ce3a8 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2336,9 +2336,10 @@ trait Symbols extends api.Symbols { self: SymbolTable => @tailrec final def originalEnclosingTopLevelClassOrDummy: Symbol = if (this eq NoSymbol) this - else if (isTopLevel) { + else if (originalOwner.isPackageClass) { if (isClass) this else moduleClass.orElse(this) - } else originalOwner.originalEnclosingTopLevelClassOrDummy + } + else originalOwner.originalEnclosingTopLevelClassOrDummy /** Is this symbol defined in the same scope and compilation unit as `that` symbol? */ def isCoDefinedWith(that: Symbol) = { diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 9f40ceff66a5..875385510885 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -92,6 +92,8 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.DiscardedExpr this.BooleanParameterType this.ForceMatchDesugar + this.NestMembers + this.NestHost this.noPrint this.typeDebug // inaccessible: this.posAssigner diff --git a/test/files/run/t6882.scala b/test/files/run/t6882.scala new file mode 100644 index 000000000000..c59dcb77089c --- /dev/null +++ b/test/files/run/t6882.scala @@ -0,0 +1,68 @@ +//> using options --target:11 +//> using jvm 11+ + +class C private (private val i: Int, private val j: Int) { + private val c = i + C.secret + + @inline def f = j * 2 +} +object C { + def unwrap(c: C): Int = c.c + + def apply(i: Int, j: Int): C = new C(i, j) + + private def secret = 5 +} + +class D(d0: String) { + private def d = d0 + def e = new E + class E { + def e = D.this.d + } +} +object D { +} + +object Top { + private def i = 42 + class Nested { + def f = i + } + def j = new Nested().f +} + +class TopHeavy { + private def i = TopHeavy.underlying +} +object TopHeavy { + private def underlying = 42 + class Nested { + def f = new TopHeavy().i + } + def j = new Nested().f +} + +object Test { + import java.lang.reflect.Modifier.{PRIVATE => Private} + def main(args: Array[String]): Unit = { + assert(C.unwrap(C(42, 27)) == 47) + for (m <- Class.forName("C$").getDeclaredMethods; n = m.getName if n.contains("secret")) { + assert(n == "secret") + assert((m.getModifiers & Private) != 0) + } + + val d = new D("mystery") + assert(d.e.e == "mystery") + for (m <- Class.forName("D").getDeclaredMethods; n = m.getName if n.contains("d")) { + assert(n == "d") + assert((m.getModifiers & Private) != 0) + } + + assert(Top.j == 42) + for (m <- Class.forName("Top$").getDeclaredMethods; n = m.getName if n.contains("i")) { + assert(n == "i") + assert((m.getModifiers & Private) != 0) + } + } +}