Skip to content

Changes to eta expansion #2701

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 9 commits into from
Jun 23, 2017
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
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case _ => false
}

/** Is tree a compiler-generated `.apply` node that refers to the
* apply of a function class?
*/
def isSyntheticApply(tree: Tree): Boolean = tree match {
case Select(qual, nme.apply) => tree.pos.end == qual.pos.end
case _ => false
}

/** Strips layers of `.asInstanceOf[T]` / `_.$asInstanceOf[T]()` from an expression */
def stripCast(tree: Tree)(implicit ctx: Context): Tree = {
def isCast(sel: Tree) = sel.symbol == defn.Any_asInstanceOf
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -809,8 +809,8 @@ class Definitions {
*/
def erasedFunctionClass(cls: Symbol): Symbol = {
val arity = scalaClassName(cls).functionArity
if (arity > 22) defn.FunctionXXLClass
else if (arity >= 0) defn.FunctionClass(arity)
if (arity > 22) FunctionXXLClass
else if (arity >= 0) FunctionClass(arity)
else NoSymbol
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,8 +668,7 @@ object messages {
private val msgPrefix = if (actualCount > expectedCount) "Too many" else "Not enough"

//TODO add def simpleParamName to ParamInfo
private val expectedArgString = fntpe
.widen.typeParams
private val expectedArgString = expectedArgs
.map(_.paramName.unexpandedName.show)
.mkString("[", ", ", "]")

Expand Down
45 changes: 42 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2006,9 +2006,48 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
// prioritize method parameter types as parameter types of the eta-expanded closure
0
else defn.functionArity(ptNorm)
else if (pt eq AnyFunctionProto) wtp.paramInfos.length
else -1
if (arity >= 0 && !tree.symbol.isConstructor)
else {
val nparams = wtp.paramInfos.length
if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams
else -1 // no eta expansion in this case
}

/** A synthetic apply should be eta-expanded if it is the apply of an implicit function
* class, and the expected type is a function type. This rule is needed so we can pass
* an implicit function to a regular function type. So the following is OK
*
* val f: implicit A => B = ???
* val g: A => B = f
*
* and the last line expands to
*
* val g: A => B = (x$0: A) => f.apply(x$0)
*
* One could be tempted not to eta expand the rhs, but that would violate the invariant
* that expressions of implicit function types are always implicit closures, which is
* exploited by ShortcutImplicits.
*
* On the other hand, the following would give an error if there is no implicit
* instance of A available.
*
* val x: AnyRef = f
*
* That's intentional, we want to fail here, otherwise some unsuccesful implicit searches
* would go undetected.
*
* Examples for these cases are found in run/implicitFuns.scala and neg/i2006.scala.
*/
def isExpandableApply =
defn.isImplicitFunctionClass(tree.symbol.maybeOwner) && defn.isFunctionType(ptNorm)

// Reasons NOT to eta expand:
// - we reference a constructor
// - we are in a patterm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo. Should be "pattern"

// - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that)
if (arity >= 0 &&
!tree.symbol.isConstructor &&
!ctx.mode.is(Mode.Pattern) &&
!(isSyntheticApply(tree) && !isExpandableApply))
typed(etaExpand(tree, wtp, arity), pt)
else if (wtp.paramInfos.isEmpty)
adaptInterpolated(tpd.Apply(tree, Nil), pt, EmptyTree)
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i1503.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ object Test {

def main(args: Array[String]) = {
(if (cond) foo1 else bar1)() // error: Unit does not take parameters
(if (cond) foo2 else bar2)(22) // error: missing arguments // error: missing arguments
(if (cond) foo2 else bar2)(22) // OK after changes to eta expansion
}
}
5 changes: 3 additions & 2 deletions tests/neg/i1716.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
object Fail {
def f(m: Option[Int]): Unit = {
m match {
case x @ Some[_] => // error
case x @ Some[_] => // error: unbound wildcard type
case _ =>
}
}
Some[_] // error
Some[_] // error: unbound wildcard type

}
12 changes: 11 additions & 1 deletion tests/neg/typedapply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@ object typedapply {

foo[Int, String, String](1, "abc") // error: wrong number of type parameters

def fuz(x: Int, y: String) = (x, y)

val x1 = fuz.curried // OK
val x2: Int => String => (Int, String) = x1

def bar(x: Int) = x

bar[Int](1) // error: does not take parameters
bar[Int](1) // error: does not take type parameters

bar.baz // error: baz is not a member of Int => Int


}


19 changes: 14 additions & 5 deletions tests/pos/i2570.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,18 @@ object Test {
def sum2(x: Int, ys: Int*) = (x /: ys)(_ + _)
val h1: ((Int, Seq[Int]) => Int) = sum2

// Not yet:
// val h1 = repeat
// val h2: (String, Int, Int) = h1
// val h3 = sum
// val h4: (Int, => Int) = h3
val h3 = repeat
val h4: ((String, Int, Int) => String) = h3
val h5 = sum
val h6: ((Int, => Int) => Int) = h5

class A
class B
class C
implicit object b extends B

def foo(x: A)(implicit bla: B): C = ???

val h7 = foo
val h8: A => C = h7
}