Skip to content

Commit 08b1e9a

Browse files
committed
Lift all non trivial prefixes for default parameters
Checking if the prefix is pure is not enough to know if we need to list the prefix. In the case of default parameters, the prefix tree might be used several times to compute the default values. This expression should only be computed once and therefore it should be lifted if there is some computation/allocation involved. Furthermore, if the prefix contains a local definition, it must be lifted to avoid duplicating the definition. A similar situation could happen with dependent default parameters. This currently works as expected.
1 parent 0800dec commit 08b1e9a

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

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

+10-2
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,18 @@ abstract class Lifter {
136136
* val x0 = pre
137137
* x0.f(...)
138138
*
139-
* unless `pre` is idempotent.
139+
* unless `pre` is idempotent reference, a `this` reference, a literal value, or a or the prefix of an `init` (`New` tree).
140+
*
141+
* Note that default arguments will refer to the prefix, we do not want
142+
* to re-evaluate a complex expression each time we access a getter.
140143
*/
141144
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
142-
if (isIdempotentExpr(tree)) tree else lift(defs, tree)
145+
tree match
146+
case tree: Literal => tree
147+
case tree: This => tree
148+
case tree: New => tree // prefix of <init> call
149+
case tree: RefTree if isIdempotentExpr(tree) => tree
150+
case _ => lift(defs, tree)
143151
}
144152

145153
/** No lifting at all */

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

+52
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,58 @@ class DottyBytecodeTests extends DottyBytecodeTest {
17331733
assertSameCode(instructions, expected)
17341734
}
17351735
}
1736+
1737+
@Test def newInPrefixesOfDefaultParam = {
1738+
val source =
1739+
s"""class A:
1740+
| def f(x: Int = 1): Int = x
1741+
|
1742+
|class Test:
1743+
| def meth1() = (new A).f()
1744+
| def meth2() = { val a = new A; a.f() }
1745+
""".stripMargin
1746+
1747+
checkBCode(source) { dir =>
1748+
val clsIn = dir.lookupName("Test.class", directory = false).input
1749+
val clsNode = loadClassNode(clsIn)
1750+
val meth1 = getMethod(clsNode, "meth1")
1751+
val meth2 = getMethod(clsNode, "meth2")
1752+
1753+
val instructions1 = instructionsFromMethod(meth1)
1754+
val instructions2 = instructionsFromMethod(meth2)
1755+
1756+
assert(instructions1 == instructions2,
1757+
"`assert` was not properly inlined in `meth1`\n" +
1758+
diffInstructions(instructions1, instructions2))
1759+
}
1760+
}
1761+
1762+
@Test def newInDependentOfDefaultParam = {
1763+
val source =
1764+
s"""class A:
1765+
| def i: Int = 1
1766+
|
1767+
|class Test:
1768+
| def f(a: A)(x: Int = a.i): Int = x
1769+
| def meth1() = f(new A)()
1770+
| def meth2() = { val a = new A; f(a)() }
1771+
""".stripMargin
1772+
1773+
checkBCode(source) { dir =>
1774+
val clsIn = dir.lookupName("Test.class", directory = false).input
1775+
val clsNode = loadClassNode(clsIn)
1776+
val meth1 = getMethod(clsNode, "meth1")
1777+
val meth2 = getMethod(clsNode, "meth2")
1778+
1779+
val instructions1 = instructionsFromMethod(meth1)
1780+
val instructions2 = instructionsFromMethod(meth2)
1781+
1782+
assert(instructions1 == instructions2,
1783+
"`assert` was not properly inlined in `meth1`\n" +
1784+
diffInstructions(instructions1, instructions2))
1785+
}
1786+
}
1787+
17361788
}
17371789

17381790
object invocationReceiversTestCode {

tests/run/i15315.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class A:
2+
def f(x: Int = 1): Int = x
3+
4+
@main def Test() =
5+
(new A{}).f()

0 commit comments

Comments
 (0)