aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/ast/Desugar.scala20
-rw-r--r--src/dotty/tools/dotc/transform/DropEmptyCompanions.scala2
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala56
-rw-r--r--test/dotc/tests.scala1
-rw-r--r--tests/neg/function-arity.scala28
-rw-r--r--tests/pos/function-arity.scala (renamed from tests/pos/i873.scala)10
-rw-r--r--tests/run/function-arity.scala8
7 files changed, 105 insertions, 20 deletions
diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala
index 87694843a..991940f10 100644
--- a/src/dotty/tools/dotc/ast/Desugar.scala
+++ b/src/dotty/tools/dotc/ast/Desugar.scala
@@ -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
*/
diff --git a/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala b/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala
index 550e3348f..65362f199 100644
--- a/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala
+++ b/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala
@@ -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
diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index 854bc2094..d39f2dd43 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -613,26 +613,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)
}
}
diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala
index 70af32bd3..421846ca2 100644
--- a/test/dotc/tests.scala
+++ b/test/dotc/tests.scala
@@ -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)
diff --git a/tests/neg/function-arity.scala b/tests/neg/function-arity.scala
new file mode 100644
index 000000000..5e0cb1058
--- /dev/null
+++ b/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
+}
diff --git a/tests/pos/i873.scala b/tests/pos/function-arity.scala
index 94f8d2c67..9ba78105b 100644
--- a/tests/pos/i873.scala
+++ b/tests/pos/function-arity.scala
@@ -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)
}
diff --git a/tests/run/function-arity.scala b/tests/run/function-arity.scala
new file mode 100644
index 000000000..6d7e5bce1
--- /dev/null
+++ b/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 ???
+ }
+}