From 264566d1bee30009911ce765468d51d46ae78d0f Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 12 Sep 2022 12:11:18 +0200 Subject: [PATCH 1/2] Peephole optimization to drop .apply from partially applied methods --- .../src/dotty/tools/dotc/typer/Typer.scala | 47 ++++++++++++------- tests/run/drop-apply-optimization.check | 1 + tests/run/drop-apply-optimization.scala | 17 +++++++ 3 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 tests/run/drop-apply-optimization.check create mode 100644 tests/run/drop-apply-optimization.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b05ba9d1ca43..ef88bf292e25 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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`, @@ -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 @@ -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) @@ -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 => @@ -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) diff --git a/tests/run/drop-apply-optimization.check b/tests/run/drop-apply-optimization.check new file mode 100644 index 000000000000..03cf09143fa9 --- /dev/null +++ b/tests/run/drop-apply-optimization.check @@ -0,0 +1 @@ +Map(0 -> 3.0, 1 -> 6.0, 2 -> 6.0) diff --git a/tests/run/drop-apply-optimization.scala b/tests/run/drop-apply-optimization.scala new file mode 100644 index 000000000000..ff32d8b8a6ab --- /dev/null +++ b/tests/run/drop-apply-optimization.scala @@ -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.toDouble + .apply: (x, y) => + x + y + println(res) + From a6b552fefeeaa64db739f0b44a441f9711103d3a Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 12 Sep 2022 13:01:08 +0200 Subject: [PATCH 2/2] Fix test for JS It seems JS prints doubles differently from Java, so avoid them in test check files. --- tests/run/drop-apply-optimization.check | 2 +- tests/run/drop-apply-optimization.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run/drop-apply-optimization.check b/tests/run/drop-apply-optimization.check index 03cf09143fa9..3462a00e53b7 100644 --- a/tests/run/drop-apply-optimization.check +++ b/tests/run/drop-apply-optimization.check @@ -1 +1 @@ -Map(0 -> 3.0, 1 -> 6.0, 2 -> 6.0) +Map(0 -> 6, 1 -> 12, 2 -> 12) diff --git a/tests/run/drop-apply-optimization.scala b/tests/run/drop-apply-optimization.scala index ff32d8b8a6ab..359e98013d29 100644 --- a/tests/run/drop-apply-optimization.scala +++ b/tests/run/drop-apply-optimization.scala @@ -10,7 +10,7 @@ class A: .groupMapReduce: (k, v) => (k + 3) % 3 .apply: (k, v) => - v.toDouble + v * 2 .apply: (x, y) => x + y println(res)