Skip to content

Commit d17088d

Browse files
committed
Generate static inline accessors module
If a class `C` needs inline accessors that would be added top-level or if the accessor is to a static member, we place it in a new invisible module `C$inline$accessors`. If the accessor location in the new scheme is not the same as the previous location, we also generate the old accessor for backward binary compatibility but do not use it. Fixes #13215 Fixes #15413
1 parent af95ceb commit d17088d

33 files changed

+381
-11
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ object StdNames {
2121
inline val AVOID_CLASH_SUFFIX = "$_avoid_name_clash_$"
2222
inline val MODULE_SUFFIX = "$"
2323
inline val TOPLEVEL_SUFFIX = "$package"
24+
inline val TOPLEVEL_INLINE_SUFFIX = "$inline$accessors"
2425
inline val NAME_JOIN = "$"
2526
inline val DEFAULT_GETTER = "$default$"
2627
inline val LOCALDUMMY_PREFIX = "<local " // owner of local blocks

compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala

+45-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package dotty.tools
22
package dotc
33
package inlines
44

5+
import scala.collection.mutable
6+
57
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
68
import Trees._
79
import core._
@@ -22,14 +24,21 @@ import transform.SymUtils.*
2224
import config.Printers.inlining
2325
import util.Property
2426
import dotty.tools.dotc.transform.TreeMapWithStages._
27+
import dotty.tools.dotc.ast.desugar.packageObjectName
28+
import dotty.tools.dotc.core.StdNames.str
29+
import dotty.tools.dotc.util.{Spans, SrcPos}
30+
import dotty.tools.dotc.typer.TopLevelExtensionModules.newAccessorModule
2531

2632
object PrepareInlineable {
2733
import tpd._
2834

2935
private val InlineAccessorsKey = new Property.Key[InlineAccessors]
36+
private val InlineAccessorsModuleKey = new Property.Key[mutable.Map[Symbol, Symbol]]
3037

3138
def initContext(ctx: Context): Context =
32-
ctx.fresh.setProperty(InlineAccessorsKey, new InlineAccessors)
39+
ctx.fresh
40+
.setProperty(InlineAccessorsKey, new InlineAccessors)
41+
.setProperty(InlineAccessorsModuleKey, mutable.Map.empty)
3342

3443
def makeInlineable(tree: Tree)(using Context): Tree =
3544
ctx.property(InlineAccessorsKey).get.makeInlineable(tree)
@@ -39,6 +48,19 @@ object PrepareInlineable {
3948
case Some(inlineAccessors) => inlineAccessors.addAccessorDefs(cls, body)
4049
case _ => body
4150

51+
def inlineAccessorsModule(topLevelClass: Symbol)(using Context): Symbol =
52+
assert(topLevelClass.asClass.owner.is(Package), topLevelClass)
53+
ctx.property(InlineAccessorsModuleKey).get.getOrElse(topLevelClass, NoSymbol)
54+
55+
def requiredInlineAccessorsModule(topLevelClass: Symbol)(using Context): Symbol =
56+
assert(topLevelClass.asClass.owner.is(Package), topLevelClass)
57+
ctx.property(InlineAccessorsModuleKey) match
58+
case Some(inlineAccessorsModule) =>
59+
inlineAccessorsModule.getOrElseUpdate(
60+
topLevelClass,
61+
newAccessorModule(str.TOPLEVEL_INLINE_SUFFIX))
62+
case None => NoSymbol
63+
4264
class InlineAccessors extends AccessProxies {
4365

4466
/** If an inline accessor name wraps a unique inline name, this is taken as indication
@@ -99,18 +121,33 @@ object PrepareInlineable {
99121
* advantage that we can re-use the receiver as is. But it is only
100122
* possible if the receiver is essentially this or an outer this, which is indicated
101123
* by the test that we can find a host for the accessor.
124+
*
125+
* @param inlineSym symbol of the inline method
126+
* @param compat use inline accessor format of 3.0-3.2
102127
*/
103-
class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) {
128+
class MakeInlineableDirect(inlineSym: Symbol, compat: Boolean) extends MakeInlineableMap(inlineSym) {
104129
def preTransform(tree: Tree)(using Context): Tree = tree match {
105130
case tree: RefTree if needsAccessor(tree.symbol) =>
106131
if (tree.symbol.isConstructor) {
107132
report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos)
108133
tree // TODO: create a proper accessor for the private constructor
109134
}
110-
else useAccessor(tree)
135+
else if compat then
136+
// Generate the accessor for backwards compatibility with 3.0-3.3
137+
val nearestHost = AccessProxies.hostForAccessorOf(tree.symbol)
138+
val host = if nearestHost.is(Package) then ctx.owner.topLevelClass else nearestHost
139+
useAccessor(tree, host)
140+
else
141+
// Generate the accessor for 3.4+
142+
val nearestHost = AccessProxies.hostForAccessorOf(tree.symbol)
143+
if nearestHost.is(Package) || (tree.symbol.owner.isStaticOwner && !nearestHost.isStaticOwner) then
144+
useAccessor(tree, requiredInlineAccessorsModule(ctx.owner.topLevelClass))
145+
else
146+
useAccessor(tree, nearestHost)
111147
case _ =>
112148
tree
113149
}
150+
114151
override def ifNoHost(reference: RefTree)(using Context): Tree = reference
115152
}
116153

@@ -226,8 +263,12 @@ object PrepareInlineable {
226263
// so no accessors are needed for them.
227264
tree
228265
else
266+
// Generate inline accessors for 3.0-3.3
267+
new MakeInlineablePassing(inlineSym).transform(
268+
new MakeInlineableDirect(inlineSym, compat = true).transform(tree))
269+
// Generate and use inline accessors for 3.4+
229270
new MakeInlineablePassing(inlineSym).transform(
230-
new MakeInlineableDirect(inlineSym).transform(tree))
271+
new MakeInlineableDirect(inlineSym, compat = false).transform(tree))
231272
}
232273
}
233274

compiler/src/dotty/tools/dotc/quoted/Interpreter.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,10 @@ object Interpreter:
349349
if !ctx.compilationUnit.isSuspendable then None
350350
else targetException match
351351
case _: NoClassDefFoundError | _: ClassNotFoundException =>
352-
val className = targetException.getMessage
353-
if className eq null then None
352+
val msg = targetException.getMessage
353+
if msg eq null then None
354354
else
355+
val className = msg.stripSuffix(str.TOPLEVEL_INLINE_SUFFIX + str.MODULE_SUFFIX)
355356
val sym = staticRef(className.toTypeName).symbol
356357
if (sym.isDefinedInCurrentRun) Some(sym) else None
357358
case _ => None

compiler/src/dotty/tools/dotc/transform/AccessProxies.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,9 @@ abstract class AccessProxies {
130130
* @param reference The original reference to the non-public symbol
131131
* @param onLHS The reference is on the left-hand side of an assignment
132132
*/
133-
def useAccessor(reference: RefTree)(using Context): Tree = {
133+
def useAccessor(reference: RefTree, accessorClass: Symbol)(using Context): Tree = {
134134
val accessed = reference.symbol.asTerm
135-
var accessorClass = hostForAccessorOf(accessed: Symbol)
136135
if (accessorClass.exists) {
137-
if accessorClass.is(Package) then
138-
accessorClass = ctx.owner.topLevelClass
139136
val accessorName = accessorNameOf(accessed.name, accessorClass)
140137
val accessorInfo =
141138
accessed.info.ensureMethodic.asSeenFrom(accessorClass.thisType, accessed.owner)
@@ -152,7 +149,7 @@ abstract class AccessProxies {
152149
report.error("Implementation restriction: cannot use private constructors in inlineable methods", tree.srcPos)
153150
tree // TODO: create a proper accessor for the private constructor
154151
}
155-
else useAccessor(tree)
152+
else useAccessor(tree, hostForAccessorOf(tree.symbol))
156153
case _ =>
157154
tree
158155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package dotty.tools.dotc.typer
2+
3+
import dotty.tools.dotc.ast.*, untpd.*
4+
import dotty.tools.dotc.core.Contexts.*
5+
import dotty.tools.dotc.core.Flags.*
6+
import dotty.tools.dotc.core.Names.*
7+
import dotty.tools.dotc.core.Scopes.*
8+
import dotty.tools.dotc.core.StdNames.*
9+
import dotty.tools.dotc.core.Symbols.*
10+
11+
/** Creation of top-level extension modules */
12+
object TopLevelExtensionModules:
13+
import tpd.*
14+
15+
/** Creates the symbols for a new extension module associated with the current
16+
* top-level class. These definitions are invisible in the source code.
17+
*
18+
* ```scala
19+
* class C
20+
* // symbols generated by `newAccessorModule("$extension")`
21+
* lazy val C$extension = new C$extension$
22+
* class C$extension$
23+
* ```
24+
*
25+
* @param suffix suffix that will be appended to the name of the top-level class
26+
* @return The class symbol of the new module
27+
*/
28+
def newAccessorModule(suffix: String)(using Context): ClassSymbol =
29+
val inlineAccessorObjectName: TermName =
30+
assert(suffix.startsWith("$"), "suffix should start with $")
31+
assert(suffix.size > 1, "suffix should start with $ followed by a name")
32+
val fileName = ctx.source.file.name
33+
val sourceName = ctx.owner.topLevelClass.name
34+
(sourceName ++ suffix).toTermName
35+
36+
val mod = newNormalizedModuleSymbol(
37+
ctx.owner.topLevelClass.owner,
38+
inlineAccessorObjectName,
39+
ModuleValCreationFlags & Invisible,
40+
ModuleClassCreationFlags & Invisible,
41+
List(defn.ObjectClass.typeRef),
42+
newScope,
43+
NoSymbol,
44+
coord = ctx.owner.topLevelClass.span
45+
)
46+
val cls = mod.moduleClass.asClass
47+
cls.enter(newConstructor(cls, Synthetic, Nil, Nil))
48+
cls
49+
50+
/** Generate a list with the ValDef and TypeDef trees of an extension module (created with `newAccessorModule`).
51+
*
52+
* ```scala
53+
* // given the moduleClassSym for `C$extension$` and `body` this generates the trees
54+
* lazy val C$extension = new C$extension$
55+
* class C$extension$:
56+
* <body*>
57+
* ```
58+
* @param moduleClassSym class symbol of the extension module
59+
* @param body list of definitions in the extension module
60+
*/
61+
def topLevelModuleDefTree(moduleClassSym: ClassSymbol, body: List[Tree])(using Context): List[Tree] =
62+
assert(moduleClassSym.owner.is(Package))
63+
val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(defn.UnitClass.typeRef), tpd.EmptyTree)
64+
val ctr = ctx.typeAssigner.assignType(untpdCtr, moduleClassSym.primaryConstructor)
65+
66+
val parents = List(TypeTree(defn.ObjectClass.typeRef))
67+
val clsDef =
68+
tpd.ClassDefWithParents(moduleClassSym.asClass, ctr, parents, body)
69+
.withSpan(moduleClassSym.span)
70+
71+
val newCls =
72+
Apply(New(ref(moduleClassSym)).select(moduleClassSym.primaryConstructor), Nil)
73+
val modVal =
74+
ValDef(moduleClassSym.companionModule.asTerm, newCls).withSpan(clsDef.span)
75+
.withSpan(moduleClassSym.companionModule.span)
76+
List(modVal, clsDef)

compiler/src/dotty/tools/dotc/typer/Typer.scala

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import util.Spans._
3434
import util.common._
3535
import util.{Property, SimpleIdentityMap, SrcPos}
3636
import Applications.{tupleComponentTypes, wrapDefs, defaultArgument}
37+
import TopLevelExtensionModules.topLevelModuleDefTree
38+
3739

3840
import collection.mutable
3941
import annotation.tailrec
@@ -2713,6 +2715,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27132715
pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted()
27142716
var stats1 = typedStats(tree.stats, pkg.moduleClass)._1
27152717
if (!ctx.isAfterTyper)
2718+
val inlineAccessorClasses = stats1.view.collect {
2719+
case tdef @ TypeDef(name, rhs) if tdef.symbol.isClass && PrepareInlineable.inlineAccessorsModule(tdef.symbol).exists =>
2720+
val inlineAccessorsModuleClass = PrepareInlineable.inlineAccessorsModule(tdef.symbol)
2721+
topLevelModuleDefTree(
2722+
inlineAccessorsModuleClass.asClass,
2723+
addAccessorDefs(inlineAccessorsModuleClass, Nil)
2724+
)
2725+
}.flatten
2726+
stats1 = stats1 ++ inlineAccessorClasses
2727+
27162728
stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1
27172729
cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef)
27182730
}

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

+77
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,83 @@ class DottyBytecodeTests extends DottyBytecodeTest {
16841684
assertSameCode(instructions, expected)
16851685
}
16861686
}
1687+
1688+
@Test
1689+
def i13215(): Unit = {
1690+
val code =
1691+
"""package foo:
1692+
| trait Bar:
1693+
| inline def baz = Baz
1694+
| private[foo] object Baz
1695+
""".stripMargin
1696+
checkBCode(code) { dir =>
1697+
val privateAccessors = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED
1698+
1699+
// For 3.0-3.3 compat
1700+
val barClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("Bar.class", directory = false).input, skipDebugInfo = false)
1701+
val accessorOld = getMethod(barClass, "foo$Bar$$inline$Baz")
1702+
assert(accessorOld.signature == "()Lfoo/Baz$;", accessorOld.signature)
1703+
assert((accessorOld.access & privateAccessors) == 0)
1704+
1705+
// For 3.4+
1706+
val accessorsClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("Bar$inline$accessors.class", directory = false).input, skipDebugInfo = false)
1707+
val accessorNew = getMethod(accessorsClass, "inline$Baz")
1708+
assert(accessorNew.signature == "()Lfoo/Baz$;", accessorNew.signature)
1709+
assert((accessorNew.access & privateAccessors) == 0)
1710+
}
1711+
}
1712+
1713+
@Test
1714+
def i15413(): Unit = {
1715+
val code =
1716+
"""import scala.quoted.*
1717+
|class Macro:
1718+
| inline def foo = Macro.fooImpl
1719+
|object Macro:
1720+
| private def fooImpl = {}
1721+
""".stripMargin
1722+
checkBCode(code) { dir =>
1723+
val privateAccessors = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED
1724+
1725+
// For 3.0-3.3 compat
1726+
val macroClass = loadClassNode(dir.lookupName("Macro.class", directory = false).input, skipDebugInfo = false)
1727+
val accessorOld = getMethod(macroClass, "Macro$$inline$fooImpl")
1728+
assert(accessorOld.signature == "()V")
1729+
assert((accessorOld.access & privateAccessors) == 0)
1730+
1731+
// For 3.4+
1732+
val accessorsClass = loadClassNode(dir.lookupName("Macro$inline$accessors.class", directory = false).input, skipDebugInfo = false)
1733+
val accessorNew = getMethod(accessorsClass, "inline$fooImpl")
1734+
assert(accessorNew.signature == "()V")
1735+
assert((accessorNew.access & privateAccessors) == 0)
1736+
}
1737+
}
1738+
1739+
@Test
1740+
def i15413b(): Unit = {
1741+
val code =
1742+
"""package foo
1743+
|class C:
1744+
| inline def baz = D.bazImpl
1745+
|object D:
1746+
| private[foo] def bazImpl = {}
1747+
""".stripMargin
1748+
checkBCode(code) { dir =>
1749+
val privateAccessors = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED
1750+
1751+
// For 3.0-3.3 compat
1752+
val barClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("C.class", directory = false).input, skipDebugInfo = false)
1753+
val accessorOld = getMethod(barClass, "inline$bazImpl$i1")
1754+
assert(accessorOld.desc == "(Lfoo/D$;)V", accessorOld.desc)
1755+
assert((accessorOld.access & privateAccessors) == 0)
1756+
1757+
// For 3.4+
1758+
val accessorsClass = loadClassNode(dir.subdirectoryNamed("foo").lookupName("C$inline$accessors.class", directory = false).input, skipDebugInfo = false)
1759+
val accessorNew = getMethod(accessorsClass, "inline$bazImpl")
1760+
assert(accessorNew.signature == "()V", accessorNew.signature)
1761+
assert((accessorNew.access & privateAccessors) == 0)
1762+
}
1763+
}
16871764
}
16881765

16891766
object invocationReceiversTestCode {

tests/pos-macros/i15413/Macro_1.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.quoted.*
2+
3+
class Macro:
4+
inline def foo = ${ Macro.fooImpl }
5+
6+
object Macro:
7+
private def fooImpl(using Quotes) = '{}

tests/pos-macros/i15413/Test_2.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def test =
2+
new Macro().foo
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scala.quoted.*
2+
3+
inline def foo = ${ fooImpl }
4+
5+
private def fooImpl(using Quotes) = '{}

tests/pos-macros/i15413b/Test_2.scala

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test = foo

tests/pos-macros/i15413c/Macro.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.quoted.*
2+
3+
class Macro:
4+
inline def foo = ${ Macro.fooImpl }
5+
6+
object Macro:
7+
private def fooImpl(using Quotes) = '{}

tests/pos-macros/i15413c/Test.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def test =
2+
new Macro().foo

tests/pos-macros/i15413d/Macro.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scala.quoted.*
2+
3+
inline def foo = ${ fooImpl }
4+
5+
private def fooImpl(using Quotes) = '{}

tests/pos-macros/i15413d/Test.scala

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
def test = foo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package foo
2+
3+
trait Bar:
4+
inline def baz = Baz
5+
6+
private[foo] object Baz
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package foo
2+
3+
trait Bar:
4+
inline def baz = Baz
5+
6+
private[foo] object Baz

0 commit comments

Comments
 (0)