Skip to content

Fix #3945: Fix eta expansion for partially applied methods #3981

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 4 commits into from
Feb 15, 2018
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3286,7 +3286,7 @@ object Types {
* `owningTree` and `owner` are used to determine whether a type-variable can be instantiated
* at some given point. See `Inferencing#interpolateUndetVars`.
*/
final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, val bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType {
final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, var bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType {

/** The permanent instance type of the variable, or NoType is none is given yet */
private[this] var myInst: Type = NoType
Expand Down
42 changes: 29 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,37 +167,53 @@ object EtaExpansion extends LiftImpure {
*
* { val xs = es; expr }
*
* If xarity matches the number of parameters in `mt`, the eta-expansion is
* The result of the eta-expansion is either (1)
*
* { val xs = es; (x1, ..., xn) => expr(x1, ..., xn) }
*
* Note that the function value's parameters are untyped, hence the type will
* be supplied by the environment (or if missing be supplied by the target
* method as a fallback). On the other hand, if `xarity` is different from
* the number of parameters in `mt`, then we cannot propagate parameter types
* from the expected type, and we fallback to using the method's original
* parameter types instead.
* or (2)
*
* In either case, the result is an untyped tree, with `es` and `expr` as typed splices.
* { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) }
*
* or (3)
*
* { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) _ }
*
* where `T1, ..., Tn` are the paremeter types of the expanded method.
*
* Case (3) applies if the method is curried, i.e. its result type is again a method
* type. Case (2) applies if the expected arity of the function type `xarity` differs
* from the number of parameters in `mt`. Case (1) applies if `mt` is uncurried
* and its number of parameters equals `xarity`. In this case we can always infer
* the parameter types later from the callee even if parameter types could not be
* inferred from the expected type. Hence, we lose nothing by omitting parameter types
* in the eta expansion. On the other hand omitting these parameters keeps the possibility
* open that different parameters are inferred from the expected type, so we keep
* more options open.
*
* In each case, the result is an untyped tree, with `es` and `expr` as typed splices.
*
* F[V](x) ==> (x => F[X])
*/
def etaExpand(tree: Tree, mt: MethodType, xarity: Int)(implicit ctx: Context): untpd.Tree = {
import untpd._
assert(!ctx.isAfterTyper)
val defs = new mutable.ListBuffer[tpd.Tree]
val lifted: Tree = TypedSplice(liftApp(defs, tree))
val isLastApplication = mt.resultType match {
case rt: MethodType => rt.isImplicitMethod
case _ => true
}
val paramTypes: List[Tree] =
if (mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree())
if (isLastApplication && mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree())
else mt.paramInfos map TypeTree
val params = (mt.paramNames, paramTypes).zipped.map((name, tpe) =>
ValDef(name, tpe, EmptyTree).withFlags(Synthetic | Param).withPos(tree.pos.startPos))
var ids: List[Tree] = mt.paramNames map (name => Ident(name).withPos(tree.pos.startPos))
if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam)
ids = ids.init :+ repeated(ids.last)
var body: Tree = Apply(lifted, ids)
mt.resultType match {
case rt: MethodType if !rt.isImplicitMethod => body = PostfixOp(body, Ident(nme.WILDCARD))
case _ =>
}
if (!isLastApplication) body = PostfixOp(body, Ident(nme.WILDCARD))
val fn = untpd.Function(params, body)
if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn
}
Expand Down
16 changes: 15 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ object Inferencing {
case Apply(fn, _) => boundVars(fn, acc)
case TypeApply(fn, targs) =>
val tvars = targs.tpes.collect {
case tvar: TypeVar if !tvar.isInstantiated => tvar
case tvar: TypeVar if !tvar.isInstantiated && targs.contains(tvar.bindingTree) => tvar
}
boundVars(fn, acc ::: tvars)
case Select(pre, _) => boundVars(pre, acc)
Expand Down Expand Up @@ -394,6 +394,20 @@ trait Inferencing { this: Typer =>
}
if (constraint.uninstVars exists qualifies) interpolate()
}

/** The uninstantiated type variables introduced somehwere in `tree` */
def uninstBoundVars(tree: Tree)(implicit ctx: Context): List[TypeVar] = {
val buf = new mutable.ListBuffer[TypeVar]
tree.foreachSubTree {
case TypeApply(_, args) =>
args.tpes.foreach {
case tv: TypeVar if !tv.isInstantiated && tree.contains(tv.bindingTree) => buf += tv
case _ =>
}
case _ =>
}
buf.toList
}
}

/** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */
Expand Down
16 changes: 13 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,7 @@ class Typer extends Namer
if (!tree.denot.isOverloaded) {
// for overloaded trees: resolve overloading before simplifying
if (tree.isDef) interpolateUndetVars(tree, tree.symbol, pt)
else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol, pt)
else if (!tree.tpe.widen.isInstanceOf[MethodOrPoly]) interpolateUndetVars(tree, NoSymbol, pt)
tree.overwriteType(tree.tpe.simplified)
}
adaptInterpolated(tree, pt)
Expand Down Expand Up @@ -2227,8 +2227,18 @@ class Typer extends Namer
if (arity >= 0 &&
!tree.symbol.isConstructor &&
!ctx.mode.is(Mode.Pattern) &&
!(isSyntheticApply(tree) && !isExpandableApply))
typed(etaExpand(tree, wtp, arity), pt)
!(isSyntheticApply(tree) && !isExpandableApply)) {
// Eta expansion interacts in tricky ways with type variable instantiation
// because it can extend the region where type variables are bound (and therefore may not
// be interpolated). To avoid premature interpolations, we need to extend the
// bindingTree of variables as we go along. Test case in pos/i3945.scala.
val boundtvs = uninstBoundVars(tree)
val uexpanded = etaExpand(tree, wtp, arity)
boundtvs.foreach(_.bindingTree = uexpanded) // make boundtvs point to uexpanded so that they are _not_ interpolated
val texpanded = typedUnadapted(uexpanded, pt)
boundtvs.foreach(_.bindingTree = texpanded) // make boundtvs point to texpanded so that they _can_ be interpolated
adapt(texpanded, pt)
}
else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol))
adaptInterpolated(tpd.Apply(tree, Nil), pt)
else if (wtp.isImplicitMethod)
Expand Down
19 changes: 19 additions & 0 deletions tests/pos/i3945.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
object Test {
def int[A](k: String => A)(s: String)(x: Int): A = ???

// composing directly: ok in scalac, now also in dotc
val c: (String => String) => (String) => (Int) => (Int) => String = (int[Int => String](_)).compose(int[String](_))

// unwrapping composition: ok in scalac, ok in dotc
val q: (String => Int => String) => (String) => (Int) => (Int => String) = int[Int => String]
val p: (String => String) => (String) => (Int) => String = int
val c2: (String => String) => (String) => (Int) => (Int) => String = q.compose(p)

class B
class C extends B
implicit def iC: C => Unit = ???

// making sure A is not instantiated before implicit search
def f[A](k: String => A)(s: String)(x: Int)(implicit y: A => Unit): A = ???
val r: (String => C) => (String) => (Int) => B = f
}