Skip to content

Commit 6fbd4cf

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 members, 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 backwards binary compatibility but do not use it. Fixes #13215 Fixes #15413
1 parent af95ceb commit 6fbd4cf

27 files changed

+265
-9
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

+55-2
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,20 @@ 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}
2530

2631
object PrepareInlineable {
2732
import tpd._
2833

2934
private val InlineAccessorsKey = new Property.Key[InlineAccessors]
35+
private val InlineAccessorsModuleKey = new Property.Key[mutable.Map[Symbol, Symbol]]
3036

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

3442
def makeInlineable(tree: Tree)(using Context): Tree =
3543
ctx.property(InlineAccessorsKey).get.makeInlineable(tree)
@@ -39,6 +47,36 @@ object PrepareInlineable {
3947
case Some(inlineAccessors) => inlineAccessors.addAccessorDefs(cls, body)
4048
case _ => body
4149

50+
def inlineAccessorsModule(topLevelClass: Symbol)(using Context): Symbol =
51+
assert(topLevelClass.asClass.owner.is(Package), topLevelClass)
52+
ctx.property(InlineAccessorsModuleKey).get.getOrElse(topLevelClass, NoSymbol)
53+
54+
def requiredInlineAccessorsModule(topLevelClass: Symbol)(using Context): Symbol =
55+
assert(topLevelClass.asClass.owner.is(Package), topLevelClass)
56+
ctx.property(InlineAccessorsModuleKey) match
57+
case Some(inlineAccessorsModule) => inlineAccessorsModule.getOrElseUpdate(topLevelClass, newAccessorModule())
58+
case None => NoSymbol
59+
60+
private def newAccessorModule()(using Context): ClassSymbol =
61+
val mod = dotc.core.Symbols.newNormalizedModuleSymbol(
62+
ctx.owner.topLevelClass.owner,
63+
inlineAccessorObjectName,
64+
ModuleValCreationFlags & Invisible,
65+
ModuleClassCreationFlags & Invisible,
66+
List(defn.ObjectClass.typeRef),
67+
dotc.core.Scopes.newScope,
68+
NoSymbol,
69+
coord = ctx.owner.topLevelClass.span
70+
)
71+
val cls = mod.moduleClass.asClass
72+
cls.enter(dotc.core.Symbols.newConstructor(cls, dotc.core.Flags.Synthetic, Nil, Nil))
73+
cls
74+
75+
private def inlineAccessorObjectName(using Context): TermName =
76+
val fileName = ctx.source.file.name
77+
val sourceName = ctx.owner.topLevelClass.name
78+
(sourceName ++ str.TOPLEVEL_INLINE_SUFFIX).toTermName
79+
4280
class InlineAccessors extends AccessProxies {
4381

4482
/** If an inline accessor name wraps a unique inline name, this is taken as indication
@@ -107,10 +145,25 @@ object PrepareInlineable {
107145
report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos)
108146
tree // TODO: create a proper accessor for the private constructor
109147
}
110-
else useAccessor(tree)
148+
else
149+
val nearestHost = AccessProxies.hostForAccessorOf(tree.symbol)
150+
val topClass = ctx.owner.topLevelClass
151+
if tree.symbol.owner.isStaticOwner && !nearestHost.isStaticOwner then
152+
// Generate the accessor for backwards compatibility with 3.0-3.2
153+
useAccessor(tree, nearestHost)
154+
// Generate and use the accessor for 3.3+
155+
useAccessor(tree, requiredInlineAccessorsModule(topClass))
156+
else if nearestHost.is(Package) then
157+
// Generate the accessor for backwards compatibility with 3.0-3.2
158+
useAccessor(tree, topClass)
159+
// Generate and use the accessor for 3.3+
160+
useAccessor(tree, requiredInlineAccessorsModule(topClass))
161+
else
162+
useAccessor(tree, nearestHost)
111163
case _ =>
112164
tree
113165
}
166+
114167
override def ifNoHost(reference: RefTree)(using Context): Tree = reference
115168
}
116169

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
}

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

+21
Original file line numberDiff line numberDiff line change
@@ -2713,6 +2713,27 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27132713
pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted()
27142714
var stats1 = typedStats(tree.stats, pkg.moduleClass)._1
27152715
if (!ctx.isAfterTyper)
2716+
val inlineAccessorClasses = stats1.collect {
2717+
case tdef @ TypeDef(name, rhs) if tdef.symbol.isClass && PrepareInlineable.inlineAccessorsModule(tdef.symbol).exists =>
2718+
val inlineAccessorsModuleClass = PrepareInlineable.inlineAccessorsModule(tdef.symbol)
2719+
val untpdCtr = untpd.DefDef(nme.CONSTRUCTOR, Nil, tpd.TypeTree(dotc.core.Symbols.defn.UnitClass.typeRef), tpd.EmptyTree)
2720+
val ctr = ctx.typeAssigner.assignType(untpdCtr, inlineAccessorsModuleClass.primaryConstructor)
2721+
2722+
val parents = List(TypeTree(defn.ObjectClass.typeRef))
2723+
val body = addAccessorDefs(inlineAccessorsModuleClass, Nil)
2724+
val clsDef =
2725+
tpd.ClassDefWithParents(inlineAccessorsModuleClass.asClass, ctr, parents, body)
2726+
.withSpan(inlineAccessorsModuleClass.span)
2727+
2728+
val newCls =
2729+
Apply(New(ref(inlineAccessorsModuleClass)).select(inlineAccessorsModuleClass.primaryConstructor), Nil)
2730+
val modVal =
2731+
ValDef(inlineAccessorsModuleClass.companionModule.asTerm, newCls).withSpan(clsDef.span)
2732+
.withSpan(inlineAccessorsModuleClass.companionModule.span)
2733+
List(modVal, clsDef)
2734+
}.flatten
2735+
stats1 = stats1 ++ inlineAccessorClasses
2736+
27162737
stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1
27172738
cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef)
27182739
}

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

+51
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,57 @@ 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.2 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.3+
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.2 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.3+
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+
}
16871738
}
16881739

16891740
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
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
3.0-3.2 inline accessor: List(Macro$$inline$fooImpl)
2+
3.3+ inline accessor: List(inline$fooImpl)
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)*/ = {}
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)*/ = {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// We first compile Macro using 3.1 to generate the old accessors
2+
// Then we compile this file to link against the old accessors
3+
// Finally we recompile Macro using the current compiler to generate a version
4+
// of Macro that contains the new accessors (and the old for backwards compat)
5+
6+
@main def Test =
7+
// Check that both accessors exist in the bytecode
8+
val barMethods =
9+
java.lang.Class.forName("Macro").getMethods()
10+
.filter(_.getName().contains("inline"))
11+
.filter(x => (x.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0)
12+
.map(_.getName())
13+
val barObjectMethods =
14+
java.lang.Class.forName("Macro$inline$accessors").getMethods()
15+
.filter(_.getName().contains("inline"))
16+
.map(_.getName())
17+
18+
println("3.0-3.2 inline accessor: " + barMethods.toList)
19+
println("3.3+ inline accessor: " + barObjectMethods.toList)
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
A.f
22
A.B.f
33
A.B.C.f
4+
A.B.D.f
5+
A.B.E.f

tests/run-macros/inline-macro-inner-object/Macro_1.scala

+14
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,19 @@ object A {
1818
'{println("A.B.C.f")}
1919
}
2020
}
21+
22+
object D {
23+
inline def f: Unit = ${impl}
24+
private[D] def impl(using Quotes): Expr[Unit] = {
25+
'{println("A.B.D.f")}
26+
}
27+
}
28+
29+
object E {
30+
inline def f: Unit = ${impl}
31+
private[A] def impl(using Quotes): Expr[Unit] = {
32+
'{println("A.B.E.f")}
33+
}
34+
}
2135
}
2236
}

tests/run-macros/inline-macro-inner-object/Test_2.scala

+2
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ object Test {
55
A.f
66
A.B.f
77
A.B.C.f
8+
A.B.D.f
9+
A.B.E.f
810
}
911
}

tests/run/i13215-compat-3.1.check

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
3.0-3.2 inline accessor: List(foo$Bar$$inline$Baz)
2+
3.3+ inline accessor: List(inline$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

tests/run/i13215-compat-3.1/A_3.scala

+6
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

tests/run/i13215-compat-3.1/B_2.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// We first compile A using 3.1 to generate the old accessors
2+
// Then we compile this file to link against the old accessors
3+
// Finally we recompile A using the current compiler to generate a version
4+
// of A that contains the new accessors (and the old for backwards compat)
5+
6+
@main def Test =
7+
val bar = new foo.Bar{}
8+
bar.baz // test that old accessor links in 3.3+
9+
10+
// Check that both accessors exist in the bytecode
11+
val barMethods =
12+
java.lang.Class.forName("foo.Bar").getMethods()
13+
.filter(_.getName().contains("inline"))
14+
.filter(x => (x.getModifiers() & java.lang.reflect.Modifier.STATIC) == 0)
15+
.map(_.getName())
16+
val barObjectMethods =
17+
java.lang.Class.forName("foo.Bar$inline$accessors").getMethods()
18+
.filter(_.getName().contains("inline"))
19+
.map(_.getName())
20+
21+
println("3.0-3.2 inline accessor: " + barMethods.toList)
22+
println("3.3+ inline accessor: " + barObjectMethods.toList)

tests/run/i13215.check

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public static foo.Baz$ foo.Bar.foo$Bar$$inline$Baz$(foo.Bar)
2+
public default foo.Baz$ foo.Bar.foo$Bar$$inline$Baz()
3+
public static foo.Baz$ foo.Bar$inline$accessors.inline$Baz()

tests/run/i13215.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package foo {
2+
trait Bar:
3+
inline def baz = Baz
4+
5+
private[foo] object Baz
6+
}
7+
8+
@main def Test: Unit =
9+
Class.forName("foo.Bar").getMethods.filter(_.getName.contains("Baz")).foreach(println)
10+
Class.forName("foo.Bar$inline$accessors").getMethods.filter(_.getName.contains("Baz")).foreach(println)

0 commit comments

Comments
 (0)