Skip to content

Commit e9d0528

Browse files
committed
Generate static inline accessors
If a class needs inline accessors that would be added top-level or if the accessor is to a static member, we place it in the top-level class as a java static method. 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 72df9cd commit e9d0528

34 files changed

+324
-19
lines changed

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

+26-5
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,6 +24,9 @@ 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._
@@ -53,8 +58,8 @@ object PrepareInlineable {
5358
/** A tree map which inserts accessors for non-public term members accessed from inlined code.
5459
*/
5560
abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert {
56-
def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName =
57-
val accName = InlineAccessorName(name)
61+
def accessorNameOf(name: TermName, site: Symbol, isStatic: Boolean)(using Context): TermName =
62+
val accName = InlineAccessorName(if isStatic then s"static$$$name".toTermName else name)
5863
if site.isExtensibleClass then accName.expandedName(site) else accName
5964

6065
/** A definition needs an accessor if it is private, protected, or qualified private
@@ -99,20 +104,32 @@ object PrepareInlineable {
99104
* advantage that we can re-use the receiver as is. But it is only
100105
* possible if the receiver is essentially this or an outer this, which is indicated
101106
* by the test that we can find a host for the accessor.
107+
*
108+
* @param inlineSym symbol of the inline method
109+
* @param compat use inline accessor format of 3.0-3.3
102110
*/
103-
class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) {
111+
class MakeInlineableDirect(inlineSym: Symbol, compat: Boolean) extends MakeInlineableMap(inlineSym) {
104112
def preTransform(tree: Tree)(using Context): Tree = tree match {
105113
case tree: RefTree if needsAccessor(tree.symbol) =>
106114
if tree.symbol.isConstructor then
107115
report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos)
108116
tree // TODO: create a proper accessor for the private constructor
109-
else
117+
else if compat then
118+
// Generate the accessor for backwards compatibility with 3.0-3.3
110119
val nearestHost = AccessProxies.hostForAccessorOf(tree.symbol)
111120
val host = if nearestHost.is(Package) then ctx.owner.topLevelClass else nearestHost
112121
useAccessor(tree, host)
122+
else
123+
// Generate the accessor for 3.4+
124+
val nearestHost = AccessProxies.hostForAccessorOf(tree.symbol)
125+
if nearestHost.is(Package) || (tree.symbol.owner.isStaticOwner && !nearestHost.isStaticOwner) then
126+
useAccessor(tree, ctx.owner.topLevelClass, isStatic = true)
127+
else
128+
useAccessor(tree, nearestHost)
113129
case _ =>
114130
tree
115131
}
132+
116133
override def ifNoHost(reference: RefTree)(using Context): Tree = reference
117134
}
118135

@@ -228,8 +245,12 @@ object PrepareInlineable {
228245
// so no accessors are needed for them.
229246
tree
230247
else
248+
// Generate inline accessors for 3.0-3.3
249+
new MakeInlineablePassing(inlineSym).transform(
250+
new MakeInlineableDirect(inlineSym, compat = true).transform(tree))
251+
// Generate and use inline accessors for 3.4+
231252
new MakeInlineablePassing(inlineSym).transform(
232-
new MakeInlineableDirect(inlineSym).transform(tree))
253+
new MakeInlineableDirect(inlineSym, compat = false).transform(tree))
233254
}
234255
}
235256

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,17 @@ class Interpreter(pos: SrcPos, classLoader0: ClassLoader)(using Context):
162162
private def interpretVarargs(args: List[Object]): Object =
163163
args.toSeq
164164

165-
private def interpretedStaticMethodCall(moduleClass: Symbol, fn: Symbol, args: List[Object]): Object = {
166-
val inst =
167-
try loadModule(moduleClass)
165+
private def interpretedStaticMethodCall(owner: Symbol, fn: Symbol, args: List[Object]): Object = {
166+
val (inst, clazz) =
167+
try
168+
if owner.is(Module) then
169+
val inst = loadModule(owner)
170+
(inst, inst.getClass)
171+
else
172+
(null, loadClass(owner.binaryClassName))
168173
catch
169174
case MissingClassDefinedInCurrentRun(sym) =>
170175
suspendOnMissing(sym, pos)
171-
val clazz = inst.getClass
172176
val name = fn.name.asTermName
173177
val method = getMethod(clazz, name, paramsSig(fn))
174178
stopIfRuntimeException(method.invoke(inst, args: _*), method)

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

+11-9
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ abstract class AccessProxies {
6767
import ast.tpd._
6868

6969
/** The name of the accessor for definition with given `name` in given `site` */
70-
def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName
70+
def accessorNameOf(name: TermName, site: Symbol, isStatic: Boolean)(using Context): TermName
7171
def needsAccessor(sym: Symbol)(using Context): Boolean
7272

7373
def ifNoHost(reference: RefTree)(using Context): Tree = {
@@ -76,20 +76,21 @@ abstract class AccessProxies {
7676
}
7777

7878
/** A fresh accessor symbol */
79-
private def newAccessorSymbol(owner: Symbol, name: TermName, info: Type, accessed: Symbol)(using Context): TermSymbol = {
79+
private def newAccessorSymbol(owner: Symbol, name: TermName, info: Type, accessed: Symbol, isStatic: Boolean)(using Context): TermSymbol = {
8080
val sym = newSymbol(owner, name, Synthetic | Method, info, coord = accessed.span).entered
8181
if accessed.is(Private) then sym.setFlag(Final)
82+
if isStatic then sym.setFlag(JavaStaticTerm)
8283
else if sym.allOverriddenSymbols.exists(!_.is(Deferred)) then sym.setFlag(Override)
8384
if accessed.hasAnnotation(defn.ExperimentalAnnot) then
8485
sym.addAnnotation(defn.ExperimentalAnnot)
8586
sym
8687
}
8788

8889
/** An accessor symbol, create a fresh one unless one exists already */
89-
protected def accessorSymbol(owner: Symbol, accessorName: TermName, accessorInfo: Type, accessed: Symbol)(using Context): Symbol = {
90+
protected def accessorSymbol(owner: Symbol, accessorName: TermName, accessorInfo: Type, accessed: Symbol, isStatic: Boolean = false)(using Context): Symbol = {
9091
def refersToAccessed(sym: Symbol) = accessedBy.get(sym).contains(accessed)
9192
owner.info.decl(accessorName).suchThat(refersToAccessed).symbol.orElse {
92-
val acc = newAccessorSymbol(owner, accessorName, accessorInfo, accessed)
93+
val acc = newAccessorSymbol(owner, accessorName, accessorInfo, accessed, isStatic)
9394
accessedBy(acc) = accessed
9495
acc
9596
}
@@ -127,16 +128,17 @@ abstract class AccessProxies {
127128
/** Create an accessor unless one exists already, and replace the original
128129
* access with a reference to the accessor.
129130
*
130-
* @param reference The original reference to the non-public symbol
131-
* @param onLHS The reference is on the left-hand side of an assignment
131+
* @param reference The original reference to the non-public symbol
132+
* @param accessorClass Class where the accessor will be generated
133+
* @param isStatic If this accessor will become a java static method
132134
*/
133-
def useAccessor(reference: RefTree, accessorClass: Symbol)(using Context): Tree = {
135+
def useAccessor(reference: RefTree, accessorClass: Symbol, isStatic: Boolean = false)(using Context): Tree = {
134136
val accessed = reference.symbol.asTerm
135137
if (accessorClass.exists) {
136-
val accessorName = accessorNameOf(accessed.name, accessorClass)
138+
val accessorName = accessorNameOf(accessed.name, accessorClass, isStatic)
137139
val accessorInfo =
138140
accessed.info.ensureMethodic.asSeenFrom(accessorClass.thisType, accessed.owner)
139-
val accessor = accessorSymbol(accessorClass, accessorName, accessorInfo, accessed)
141+
val accessor = accessorSymbol(accessorClass, accessorName, accessorInfo, accessed, isStatic)
140142
rewire(reference, accessor)
141143
}
142144
else ifNoHost(reference)

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ class ProtectedAccessors extends MiniPhase {
6464

6565
private class Accessors extends AccessProxies {
6666
val insert: Insert = new Insert {
67-
def accessorNameOf(name: TermName, site: Symbol)(using Context): TermName = ProtectedAccessorName(name)
67+
def accessorNameOf(name: TermName, site: Symbol, isStatic: Boolean)(using Context): TermName =
68+
assert(!isStatic)
69+
ProtectedAccessorName(name)
6870
def needsAccessor(sym: Symbol)(using Context) = ProtectedAccessors.needsAccessor(sym)
6971

7072
override def ifNoHost(reference: RefTree)(using Context): Tree = {

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

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import util.common._
3535
import util.{Property, SimpleIdentityMap, SrcPos}
3636
import Applications.{tupleComponentTypes, wrapDefs, defaultArgument}
3737

38+
3839
import collection.mutable
3940
import annotation.tailrec
4041
import Implicits._

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

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

16871761
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
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package myMacro
2+
3+
import scala.quoted.*
4+
5+
class Macro:
6+
inline def foo = ${ Foo.impl }
7+
inline def bar = ${ Bar.impl }
8+
inline def baz = ${ foo.Foo.impl }
9+
10+
object Foo:
11+
private[myMacro] def impl(using Quotes) = '{}
12+
13+
object Bar:
14+
private[myMacro] def impl(using Quotes) = '{1}
15+
16+
package foo:
17+
object Foo:
18+
private[myMacro] def impl(using Quotes) = '{"abc"}

tests/pos-macros/i15413e/Test_2.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import myMacro.Macro
2+
3+
def test(m: Macro) =
4+
m.foo
5+
m.bar
6+
m.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
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,25 @@
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: foo.Bar = new foo.Bar{}
8+
bar.baz // test that old accessor links in 3.4+
9+
10+
// Check that both accessors exist in the bytecode
11+
val inlineAccessors =
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+
.toList
17+
val staticInlineAccessors =
18+
java.lang.Class.forName("foo.Bar").getMethods()
19+
.filter(_.getName().contains("inline"))
20+
.filter(x => (x.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
21+
.map(_.getName())
22+
.toList
23+
24+
println("3.0-3.3 inline accessor: " + inlineAccessors)
25+
println("3.4+ inline accessor: " + staticInlineAccessors)
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
3.0-3.3 inline accessor: List(Macro$$inline$fooImpl)
2+
3.4+ inline accessor: List(Macro$$inline$static$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)*/ = {}

0 commit comments

Comments
 (0)