Skip to content
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

Implement auto tupling of function arguments #898

Merged
merged 7 commits into from Feb 18, 2016
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/dotty/tools/dotc/ast/Desugar.scala
Expand Up @@ -588,6 +588,26 @@ object desugar {
Function(params, Match(selector, cases))
}

/** Map n-ary function `(p1, ..., pn) => body` where n != 1 to unary function as follows:
*
* x$1 => {
* def p1 = x$1._1
* ...
* def pn = x$1._n
* body
* }
*/
def makeTupledFunction(params: List[ValDef], body: Tree)(implicit ctx: Context): Tree = {
val param = makeSyntheticParameter()
def selector(n: Int) = Select(refOfDef(param), nme.selectorName(n))
val vdefs =
params.zipWithIndex.map{
case (param, idx) =>
DefDef(param.name, Nil, Nil, TypeTree(), selector(idx)).withPos(param.pos)
}
Function(param :: Nil, Block(vdefs, body))
}

/** Add annotation with class `cls` to tree:
* tree @cls
*/
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/transform/DropEmptyCompanions.scala
Expand Up @@ -40,7 +40,7 @@ class DropEmptyCompanions extends MiniPhaseTransform { thisTransform =>
case TypeDef(_, impl: Template) if tree.symbol.is(SyntheticModule) &&
tree.symbol.companionClass.exists &&
impl.body.forall(_.symbol.isPrimaryConstructor) =>
println(i"removing ${tree.symbol}")
ctx.log(i"removing ${tree.symbol}")
true
case _ =>
false
Expand Down
56 changes: 37 additions & 19 deletions src/dotty/tools/dotc/typer/Typer.scala
Expand Up @@ -611,26 +611,44 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
if (protoFormals.length == params.length) protoFormals(i)
else errorType(i"wrong number of parameters, expected: ${protoFormals.length}", tree.pos)

val inferredParams: List[untpd.ValDef] =
for ((param, i) <- params.zipWithIndex) yield
if (!param.tpt.isEmpty) param
else cpy.ValDef(param)(
tpt = untpd.TypeTree(
inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false)))

// Define result type of closure as the expected type, thereby pushing
// down any implicit searches. We do this even if the expected type is not fully
// defined, which is a bit of a hack. But it's needed to make the following work
// (see typers.scala and printers/PlainPrinter.scala for examples).
//
// def double(x: Char): String = s"$x$x"
// "abc" flatMap double
//
val resultTpt = protoResult match {
case WildcardType(_) => untpd.TypeTree()
case _ => untpd.TypeTree(protoResult)
/** Is `formal` a product type which is elementwise compatible with `params`? */
def ptIsCorrectProduct(formal: Type) = {
val pclass = defn.ProductNType(params.length).symbol
isFullyDefined(formal, ForceDegree.noBottom) &&
formal.derivesFrom(pclass) &&
formal.baseArgTypes(pclass).corresponds(params) {
(argType, param) =>
param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe
}
}
typed(desugar.makeClosure(inferredParams, fnBody, resultTpt), pt)

val desugared =
if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) {
desugar.makeTupledFunction(params, fnBody)
}
else {
val inferredParams: List[untpd.ValDef] =
for ((param, i) <- params.zipWithIndex) yield
if (!param.tpt.isEmpty) param
else cpy.ValDef(param)(
tpt = untpd.TypeTree(
inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false)))

// Define result type of closure as the expected type, thereby pushing
// down any implicit searches. We do this even if the expected type is not fully
// defined, which is a bit of a hack. But it's needed to make the following work
// (see typers.scala and printers/PlainPrinter.scala for examples).
//
// def double(x: Char): String = s"$x$x"
// "abc" flatMap double
//
val resultTpt = protoResult match {
case WildcardType(_) => untpd.TypeTree()
case _ => untpd.TypeTree(protoResult)
}
desugar.makeClosure(inferredParams, fnBody, resultTpt)
}
typed(desugared, pt)
}
}

Expand Down
1 change: 1 addition & 0 deletions test/dotc/tests.scala
Expand Up @@ -111,6 +111,7 @@ class tests extends CompilerTest {
@Test def neg_abstractOverride() = compileFile(negDir, "abstract-override", xerrors = 2)
@Test def neg_blockescapes() = compileFile(negDir, "blockescapesNeg", xerrors = 1)
@Test def neg_bounds() = compileFile(negDir, "bounds", xerrors = 2)
@Test def neg_functionArity() = compileFile(negDir, "function-arity", xerrors = 7)
@Test def neg_typedapply() = compileFile(negDir, "typedapply", xerrors = 3)
@Test def neg_typedIdents() = compileDir(negDir, "typedIdents", xerrors = 2)
@Test def neg_assignments() = compileFile(negDir, "assignments", xerrors = 3)
Expand Down
28 changes: 28 additions & 0 deletions tests/neg/function-arity.scala
@@ -0,0 +1,28 @@
object Test {

// From #873:

trait X extends Function1[Int, String]
implicit def f2x(f: Function1[Int, String]): X = ???
({case _ if "".isEmpty => 0} : X) // error: expected String, found Int

// Tests where parameter list cannot be made into a pattern

def unary[T](x: T => Unit) = ???
unary((x, y) => ()) // error

unary[(Int, Int)]((x, y) => ())

unary[(Int, Int)](() => ()) // error
unary[(Int, Int)]((x, y, _) => ()) // error

unary[(Int, Int)]((x: String, y) => ()) // error

def foo(a: Tuple2[Int, Int] => String): String = ""
def foo(a: Any => String) = ()
foo((a: Int, b: String) => a + b) // error: none of the overloaded alternatives of method foo match arguments (Int, Int)
}
object jasonComment {
implicit def i2s(i: Int): String = i.toString
((x: String, y: String) => 42) : (((Int, Int)) => String) // error
}
10 changes: 10 additions & 0 deletions tests/pos/i873.scala → tests/pos/function-arity.scala
Expand Up @@ -7,4 +7,14 @@ object Test {
({case _ if "".isEmpty => ""} : X) // allowed, implicit view used to adapt

// ({case _ if "".isEmpty => 0} : X) // expected String, found Int

def unary[T](a: T, b: T, f: ((T, T)) => T): T = f((a, b))
unary(1, 2, (x, y) => x)
unary(1, 2, (x: Int, y) => x)
unary(1, 2, (x: Int, y: Int) => x)

val xs = List(1, 2, 3)
def f(x: Int, y: Int) = x * y
xs.zipWithIndex.map(_ + _)
xs.zipWithIndex.map(f)
}
8 changes: 8 additions & 0 deletions tests/run/function-arity.scala
@@ -0,0 +1,8 @@
object Test {
class T[A] { def foo(f: (=> A) => Int) = f(???) }

def main(args: Array[String]): Unit = {
new T[(Int, Int)].foo((ii) => 0)
new T[(Int, Int)].foo((x, y) => 0) // check that this does not run into ???
}
}