Skip to content

Commit

Permalink
Peephole optimization to drop .apply from partially applied methods (#…
Browse files Browse the repository at this point in the history
…16022)

This optimization was postulated for making a proposed fewerBraces idiom
efficient.
  • Loading branch information
KacperFKorban authored Sep 14, 2022
2 parents a3c0bef + a6b552f commit 684ae79
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 16 deletions.
47 changes: 31 additions & 16 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
checkLegalValue(select, pt)
ConstFold(select)
else if selName == nme.apply && qual.tpe.widen.isInstanceOf[MethodType] then
// Simplify `m.apply(...)` to `m(...)`
qual
else if couldInstantiateTypeVar(qual.tpe.widen) then
// there's a simply visible type variable in the result; try again with a more defined qualifier type
// There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`,
Expand Down Expand Up @@ -3665,6 +3668,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
|| Feature.warnOnMigration(MissingEmptyArgumentList(sym.show), tree.srcPos, version = `3.0`)
&& { patch(tree.span.endPos, "()"); true }

/** If this is a selection prototype of the form `.apply(...): R`, return the nested
* function prototype `(...)R`. Otherwise `pt`.
*/
def ptWithoutRedundantApply: Type = pt.revealIgnored match
case SelectionProto(nme.apply, mpt, _, _) =>
mpt.revealIgnored match
case fpt: FunProto => fpt
case _ => pt
case _ => pt

// Reasons NOT to eta expand:
// - we reference a constructor
// - we reference a typelevel method
Expand All @@ -3676,13 +3689,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
&& !ctx.mode.is(Mode.Pattern)
&& !(isSyntheticApply(tree) && !functionExpected)
then
if (!defn.isFunctionType(pt))
pt match {
case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) =>
report.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos)
case _ =>
}
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)
val pt1 = ptWithoutRedundantApply
if pt1 ne pt then
// Ignore `.apply` in `m.apply(...)`; it will later be simplified in typedSelect to `m(...)`
adapt1(tree, pt1, locked)
else
if (!defn.isFunctionType(pt))
pt match {
case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) =>
report.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos)
case _ =>
}
simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked)
else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol))
readaptSimplified(tpd.Apply(tree, Nil))
else if (wtp.isImplicitMethod)
Expand Down Expand Up @@ -3791,11 +3809,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def adaptNoArgs(wtp: Type): Tree = {
val ptNorm = underlyingApplied(pt)
def functionExpected = defn.isFunctionType(ptNorm)
def needsEta = pt match {
case _: SingletonType => false
case IgnoredProto(_: FunOrPolyProto) => false
def needsEta = pt.revealIgnored match
case _: SingletonType | _: FunOrPolyProto => false
case _ => true
}
var resMatch: Boolean = false
wtp match {
case wtp: ExprType =>
Expand All @@ -3812,17 +3828,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case wtp: MethodType if needsEta =>
val funExpected = functionExpected
val arity =
if (funExpected)
if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none))
if funExpected then
if !isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none) then
// if method type is fully defined, but expected type is not,
// prioritize method parameter types as parameter types of the eta-expanded closure
0
else defn.functionArity(ptNorm)
else {
else
val nparams = wtp.paramInfos.length
if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams
if nparams > 0 || pt.eq(AnyFunctionProto) then nparams
else -1 // no eta expansion in this case
}
adaptNoArgsUnappliedMethod(wtp, funExpected, arity)
case _ =>
adaptNoArgsOther(wtp, functionExpected)
Expand Down
1 change: 1 addition & 0 deletions tests/run/drop-apply-optimization.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Map(0 -> 6, 1 -> 12, 2 -> 12)
17 changes: 17 additions & 0 deletions tests/run/drop-apply-optimization.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import language.experimental.fewerBraces
class A:
def f(x: Int)(y: Int): Int = x + y

f(22).apply(33)

@main def Test =
val theMap = Map(-1 -> 1, -2 -> 2, 0 -> 3, 1 -> 4, 2 -> 5)
val res = theMap
.groupMapReduce: (k, v) =>
(k + 3) % 3
.apply: (k, v) =>
v * 2
.apply: (x, y) =>
x + y
println(res)

0 comments on commit 684ae79

Please sign in to comment.