Skip to content

Commit 2a8c275

Browse files
committed
Keep inlined final vals in generated code
Though final vals with constants are guarantieed to be inlined in Dotty, we need to keep the definition in case these are used from Scala 2. Scala 2 misses some inlining oportunities.
1 parent 4379077 commit 2a8c275

File tree

6 files changed

+28
-27
lines changed

6 files changed

+28
-27
lines changed

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
114114
EmptyTree
115115
}
116116

117-
if (sym.is(Accessor, butNot = NoFieldNeeded)) {
117+
val constantFinalVal = sym.isAllOf(Accessor | Final, butNot = Mutable) && tree.rhs.isInstanceOf[Literal]
118+
119+
if (sym.is(Accessor, butNot = NoFieldNeeded) && !constantFinalVal) {
118120
val field = sym.field.orElse(newField).asTerm
119121

120122
def adaptToField(tree: Tree): Tree =

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -869,11 +869,9 @@ trait Checking {
869869
/** Check that `tree` can be right hand-side or argument to `inline` value or parameter. */
870870
def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = {
871871
if sym.is(Inline, butNot = DeferredOrTermParamOrAccessor) && !ctx.erasedTypes && !Inliner.inInlineMethod then
872-
// final vals can be marked inline even if they're not pure, see Typer#patchFinalVals
873-
val purityLevel = if (sym.is(Final)) Idempotent else Pure
874872
tpt.tpe.widenTermRefExpr.dealias.normalized match
875873
case tp: ConstantType =>
876-
if !(exprPurity(tree) >= purityLevel) then
874+
if !(exprPurity(tree) >= Pure) then
877875
ctx.error(em"inline value must be pure", tree.sourcePos)
878876
case _ =>
879877
val pos = if tpt.span.isZeroExtent then tree.sourcePos else tpt.sourcePos

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

+1
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,7 @@ class Namer { typer: Typer =>
14641464

14651465
// println(s"final inherited for $sym: ${inherited.toString}") !!!
14661466
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
1467+
// TODO Scala 3.1: only check for inline vals (no final ones)
14671468
def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable)
14681469

14691470
// Widen rhs type and eliminate `|' but keep ConstantTypes if

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

-21
Original file line numberDiff line numberDiff line change
@@ -1856,30 +1856,9 @@ class Typer extends Namer
18561856
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
18571857
checkSignatureRepeatedParam(sym)
18581858
checkInlineConformant(tpt1, rhs1, sym)
1859-
patchFinalVals(vdef1)
18601859
vdef1.setDefTree
18611860
}
18621861

1863-
/** Adds inline to final vals with idempotent rhs
1864-
*
1865-
* duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field
1866-
* and instead return the value. This seemingly minor optimization has huge effect on initialization
1867-
* order and the values that can be observed during superconstructor call
1868-
*
1869-
* see remark about idempotency in TreeInfo#constToLiteral
1870-
*/
1871-
private def patchFinalVals(vdef: ValDef)(using Context): Unit = {
1872-
def isFinalInlinableVal(sym: Symbol): Boolean =
1873-
sym.is(Final, butNot = Mutable) &&
1874-
isIdempotentExpr(vdef.rhs) /* &&
1875-
ctx.scala2Mode (stay compatible with Scala2 for now) */
1876-
val sym = vdef.symbol
1877-
sym.info match {
1878-
case info: ConstantType if isFinalInlinableVal(sym) && !ctx.settings.YnoInline.value => sym.setFlag(Inline)
1879-
case _ =>
1880-
}
1881-
}
1882-
18831862
def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = {
18841863
if (!sym.info.exists) { // it's a discarded synthetic case class method, drop it
18851864
assert(sym.is(Synthetic) && desugar.isRetractableCaseClassMethodName(sym.name))

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ class InlineBytecodeTests extends DottyBytecodeTest {
345345
}
346346

347347
// Testing that a is not boxed
348-
@Test def i4522 = {
348+
@Test def i4522 = {
349349
val source = """class Foo {
350350
| def test: Int = {
351351
| var a = 10
@@ -480,7 +480,28 @@ class InlineBytecodeTests extends DottyBytecodeTest {
480480
val expected = List(Ldc(LDC, 5.0), Op(DRETURN))
481481
assert(instructions == expected,
482482
"`divide` was not properly inlined in `test`\n" + diffInstructions(instructions, expected))
483+
}
484+
}
485+
486+
@Test def finalVals = {
487+
val source = """class Test:
488+
| final val a = 1 // should be inlined but not erased
489+
| inline val b = 2 // should be inlined and erased
490+
| def test: Int = a + b
491+
""".stripMargin
492+
493+
checkBCode(source) { dir =>
494+
val clsIn = dir.lookupName("Test.class", directory = false).input
495+
val clsNode = loadClassNode(clsIn)
496+
497+
val fun = getMethod(clsNode, "test")
498+
val instructions = instructionsFromMethod(fun)
499+
val expected = List(Op(ICONST_3), Op(IRETURN))
500+
assert(instructions == expected,
501+
"`a and b were not properly inlined in `test`\n" + diffInstructions(instructions, expected))
483502

503+
val methods = clsNode.methods.asScala.toList.map(_.name)
504+
assert(methods == List("<init>", "a", "test"), clsNode.methods.asScala.toList.map(_.name))
484505
}
485506
}
486507

tests/run/erased-inline-vals.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class D:
4141
assert(classOf[B].getDeclaredFields.isEmpty)
4242

4343
assert(classOf[C].getDeclaredMethods.size == 2)
44-
assert(classOf[C].getDeclaredFields.size == 1)
44+
assert(classOf[C].getDeclaredFields.isEmpty)
4545

4646
assert(classOf[D].getDeclaredMethods.isEmpty)
4747
assert(classOf[D].getFields.isEmpty)

0 commit comments

Comments
 (0)