Skip to content

Commit b3767c8

Browse files
committed
Fix #3945: Fix eta expansion for partially applied methods
1 parent 3365ed3 commit b3767c8

File tree

2 files changed

+38
-13
lines changed

2 files changed

+38
-13
lines changed

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

+27-13
Original file line numberDiff line numberDiff line change
@@ -167,37 +167,51 @@ object EtaExpansion extends LiftImpure {
167167
*
168168
* { val xs = es; expr }
169169
*
170-
* If xarity matches the number of parameters in `mt`, the eta-expansion is
170+
* The result of the eta-expansion is either (1)
171171
*
172172
* { val xs = es; (x1, ..., xn) => expr(x1, ..., xn) }
173173
*
174-
* Note that the function value's parameters are untyped, hence the type will
175-
* be supplied by the environment (or if missing be supplied by the target
176-
* method as a fallback). On the other hand, if `xarity` is different from
177-
* the number of parameters in `mt`, then we cannot propagate parameter types
178-
* from the expected type, and we fallback to using the method's original
179-
* parameter types instead.
174+
* or (2)
180175
*
181-
* In either case, the result is an untyped tree, with `es` and `expr` as typed splices.
176+
* { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) }
177+
*
178+
* or (3)
179+
*
180+
* { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) _ }
181+
*
182+
* where `T1, ..., Tn` are the paremeter types of the expanded method.
183+
*
184+
* Case (3) applies if the method is curried, i.e. its result type is again a method
185+
* type. Case (2) applies if the expected arity of the function type `xarity` differs
186+
* from the number of parameters in `mt`. Case (1) applies if `mt` is uncurried
187+
* and its number of parameters equals `xarity`. In this case we can always infer
188+
* the parameter types later from the callee even if parameter types could not be
189+
* inferred from the expected type. Hence, we lose nothing by omitting parameter types
190+
* in the eta expansion. On the other hand omitting these parameters keeps the possibility
191+
* open that different parameters are inferred from the expected type, so we keep
192+
* more options open.
193+
*
194+
* In each case, the result is an untyped tree, with `es` and `expr` as typed splices.
182195
*/
183196
def etaExpand(tree: Tree, mt: MethodType, xarity: Int)(implicit ctx: Context): untpd.Tree = {
184197
import untpd._
185198
assert(!ctx.isAfterTyper)
186199
val defs = new mutable.ListBuffer[tpd.Tree]
187200
val lifted: Tree = TypedSplice(liftApp(defs, tree))
201+
val isLastApplication = mt.resultType match {
202+
case rt: MethodType => rt.isImplicitMethod
203+
case _ => true
204+
}
188205
val paramTypes: List[Tree] =
189-
if (mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree())
206+
if (isLastApplication && mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree())
190207
else mt.paramInfos map TypeTree
191208
val params = (mt.paramNames, paramTypes).zipped.map((name, tpe) =>
192209
ValDef(name, tpe, EmptyTree).withFlags(Synthetic | Param).withPos(tree.pos.startPos))
193210
var ids: List[Tree] = mt.paramNames map (name => Ident(name).withPos(tree.pos.startPos))
194211
if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam)
195212
ids = ids.init :+ repeated(ids.last)
196213
var body: Tree = Apply(lifted, ids)
197-
mt.resultType match {
198-
case rt: MethodType if !rt.isImplicitMethod => body = PostfixOp(body, Ident(nme.WILDCARD))
199-
case _ =>
200-
}
214+
if (!isLastApplication) body = PostfixOp(body, Ident(nme.WILDCARD))
201215
val fn = untpd.Function(params, body)
202216
if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn
203217
}

tests/pos/i3945.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
object Test {
2+
def int[A](k: String => A)(s: String)(x: Int): A = ???
3+
4+
// composing directly: ok in scalac, error in dotc
5+
val c: (String => String) => (String) => (Int) => (Int) => String = (int[Int => String](_)).compose(int[String](_))
6+
7+
// unwrapping composition: ok in scalac, ok in dotc
8+
val q: (String => Int => String) => (String) => (Int) => (Int => String) = int[Int => String]
9+
val p: (String => String) => (String) => (Int) => String = int[String]
10+
val c2: (String => String) => (String) => (Int) => (Int) => String = q.compose(p)
11+
}

0 commit comments

Comments
 (0)