Skip to content

Commit 684ae79

Browse files
Peephole optimization to drop .apply from partially applied methods (#16022)
This optimization was postulated for making a proposed fewerBraces idiom efficient.
2 parents a3c0bef + a6b552f commit 684ae79

File tree

3 files changed

+49
-16
lines changed

3 files changed

+49
-16
lines changed

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

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
614614
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
615615
checkLegalValue(select, pt)
616616
ConstFold(select)
617+
else if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
618+
// Simplify `m.apply(...)` to `m(...)`
619+
qual
617620
else if couldInstantiateTypeVar(qual.tpe.widen) then
618621
// there's a simply visible type variable in the result; try again with a more defined qualifier type
619622
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
@@ -3665,6 +3668,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
36653668
|| Feature.warnOnMigration(MissingEmptyArgumentList(sym.show), tree.srcPos, version = `3.0`)
36663669
&& { patch(tree.span.endPos, "()"); true }
36673670

3671+
/** If this is a selection prototype of the form `.apply(...): R`, return the nested
3672+
* function prototype `(...)R`. Otherwise `pt`.
3673+
*/
3674+
def ptWithoutRedundantApply: Type = pt.revealIgnored match
3675+
case SelectionProto(nme.apply, mpt, _, _) =>
3676+
mpt.revealIgnored match
3677+
case fpt: FunProto => fpt
3678+
case _ => pt
3679+
case _ => pt
3680+
36683681
// Reasons NOT to eta expand:
36693682
// - we reference a constructor
36703683
// - we reference a typelevel method
@@ -3676,13 +3689,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
36763689
&& !ctx.mode.is(Mode.Pattern)
36773690
&& !(isSyntheticApply(tree) && !functionExpected)
36783691
then
3679-
if (!defn.isFunctionType(pt))
3680-
pt match {
3681-
case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) =>
3682-
report.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos)
3683-
case _ =>
3684-
}
3685-
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)
3692+
val pt1 = ptWithoutRedundantApply
3693+
if pt1 ne pt then
3694+
// Ignore `.apply` in `m.apply(...)`; it will later be simplified in typedSelect to `m(...)`
3695+
adapt1(tree, pt1, locked)
3696+
else
3697+
if (!defn.isFunctionType(pt))
3698+
pt match {
3699+
case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) =>
3700+
report.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos)
3701+
case _ =>
3702+
}
3703+
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)
36863704
else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol))
36873705
readaptSimplified(tpd.Apply(tree, Nil))
36883706
else if (wtp.isImplicitMethod)
@@ -3791,11 +3809,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
37913809
def adaptNoArgs(wtp: Type): Tree = {
37923810
val ptNorm = underlyingApplied(pt)
37933811
def functionExpected = defn.isFunctionType(ptNorm)
3794-
def needsEta = pt match {
3795-
case _: SingletonType => false
3796-
case IgnoredProto(_: FunOrPolyProto) => false
3812+
def needsEta = pt.revealIgnored match
3813+
case _: SingletonType | _: FunOrPolyProto => false
37973814
case _ => true
3798-
}
37993815
var resMatch: Boolean = false
38003816
wtp match {
38013817
case wtp: ExprType =>
@@ -3812,17 +3828,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
38123828
case wtp: MethodType if needsEta =>
38133829
val funExpected = functionExpected
38143830
val arity =
3815-
if (funExpected)
3816-
if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none))
3831+
if funExpected then
3832+
if !isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none) then
38173833
// if method type is fully defined, but expected type is not,
38183834
// prioritize method parameter types as parameter types of the eta-expanded closure
38193835
0
38203836
else defn.functionArity(ptNorm)
3821-
else {
3837+
else
38223838
val nparams = wtp.paramInfos.length
3823-
if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams
3839+
if nparams > 0 || pt.eq(AnyFunctionProto) then nparams
38243840
else -1 // no eta expansion in this case
3825-
}
38263841
adaptNoArgsUnappliedMethod(wtp, funExpected, arity)
38273842
case _ =>
38283843
adaptNoArgsOther(wtp, functionExpected)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Map(0 -> 6, 1 -> 12, 2 -> 12)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import language.experimental.fewerBraces
2+
class A:
3+
def f(x: Int)(y: Int): Int = x + y
4+
5+
f(22).apply(33)
6+
7+
@main def Test =
8+
val theMap = Map(-1 -> 1, -2 -> 2, 0 -> 3, 1 -> 4, 2 -> 5)
9+
val res = theMap
10+
.groupMapReduce: (k, v) =>
11+
(k + 3) % 3
12+
.apply: (k, v) =>
13+
v * 2
14+
.apply: (x, y) =>
15+
x + y
16+
println(res)
17+

0 commit comments

Comments
 (0)