Skip to content

Commit 2917bbb

Browse files
Merge pull request #9261 from dotty-staging/keep-inlined-final-vals-in-generated-code
Keep inlined final vals in generated code
2 parents aa98621 + 2a8c275 commit 2917bbb

File tree

6 files changed

+28
-27
lines changed

6 files changed

+28
-27
lines changed

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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 1 addition & 3 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 0 additions & 21 deletions
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

Lines changed: 22 additions & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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)