Skip to content

Commit 77c1bb0

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 8c5a58f commit 77c1bb0

File tree

6 files changed

+30
-27
lines changed

6 files changed

+30
-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 inlinedFinalVal = sym.isAllOf(Accessor | Final, butNot = Mutable) && tree.rhs.isInstanceOf[Literal]
118+
119+
if (sym.is(Accessor, butNot = NoFieldNeeded) && !inlinedFinalVal) {
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

+24-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
@@ -418,4 +418,27 @@ class InlineBytecodeTests extends DottyBytecodeTest {
418418

419419
}
420420
}
421+
422+
@Test def finalVals = {
423+
val source = """class Test:
424+
| final val a = 1 // should be inlined but not erased
425+
| inline val b = 2 // should be inlined and erased
426+
| def test: Int = a + b
427+
""".stripMargin
428+
429+
checkBCode(source) { dir =>
430+
val clsIn = dir.lookupName("Test.class", directory = false).input
431+
val clsNode = loadClassNode(clsIn)
432+
433+
val fun = getMethod(clsNode, "test")
434+
val instructions = instructionsFromMethod(fun)
435+
val expected = List(Op(ICONST_3), Op(IRETURN))
436+
assert(instructions == expected,
437+
"`a and b were not properly inlined in `test`\n" + diffInstructions(instructions, expected))
438+
439+
val methods = clsNode.methods.asScala.toList.map(_.name)
440+
assert(methods == List("<init>", "a", "test"), clsNode.methods.asScala.toList.map(_.name))
441+
}
442+
}
443+
421444
}

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)