diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a73c73863606..765dde1f697e 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`, @@ -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 @@ -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) @@ -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 => @@ -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) diff --git a/tests/run/drop-apply-optimization.check b/tests/run/drop-apply-optimization.check new file mode 100644 index 000000000000..3462a00e53b7 --- /dev/null +++ b/tests/run/drop-apply-optimization.check @@ -0,0 +1 @@ +Map(0 -> 6, 1 -> 12, 2 -> 12) diff --git a/tests/run/drop-apply-optimization.scala b/tests/run/drop-apply-optimization.scala new file mode 100644 index 000000000000..359e98013d29 --- /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 * 2 + .apply: (x, y) => + x + y + println(res) +