Skip to content

Peephole optimization to drop .apply from partially applied methods #16022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -3699,6 +3702,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 @@ -3710,13 +3723,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 @@ -3825,11 +3843,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 @@ -3846,17 +3862,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)