effekt-lang / effekt

A language with lexical effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
334 stars 24 forks source link

Nicer error messages on argument list / tuple mismatch #668

Open jiribenes opened 2 weeks ago

jiribenes commented 2 weeks ago

There are two general situations where the users confuse argument lists with tuples. We could potentially special-case the messages here:

1. Want a tuple, got an argument list

def foo { f: (Int, Int) => Bool }: Unit = <>

def main() = foo { case (x, y) => true }
//                 ~~~~~~~~~~~~~~~~~~~
// Wrong number of value arguments, given 1, but function expects 2.
// ~> Did you mean `(x, y) => true`?

2. Want two arguments, got a tuple

def bar { f: ((Int, Int)) => Bool }: Unit = <>

def main() = bar { (x, y) => true }
//                 ~~~~~~~~~~~~~~~
// Wrong number of value arguments, given 2, but function expects 1.
// ~> Did you mean `case (x, y) => true`

Of course, there's an issue with baz((1, 2)) vs baz(1, 2) as well :)

jiribenes commented 2 weeks ago

A truly rudimentary and extraordinarily hacky fix for the two explicit cases presented above is:

diff --git i/effekt/shared/src/main/scala/effekt/Typer.scala w/effekt/shared/src/main/scala/effekt/Typer.scala
index 48849181..04eadc5e 100644
--- i/effekt/shared/src/main/scala/effekt/Typer.scala
+++ w/effekt/shared/src/main/scala/effekt/Typer.scala
@@ -893,8 +893,24 @@ object Typer extends Phase[NameResolved, Typechecked] {
       if (tps.size != tparams.size)
         Context.abort(s"Wrong number of type arguments, given ${tparams.size}, but function expects ${tps.size}.")

-      if (vps.size != vparams.size)
+      if (vps.size != vparams.size) {
+        vps match
+          case (tuptpe @ ValueTypeApp(TypeConstructor.Record(name, tupparams, _), _)) :: Nil
+            if name.name.startsWith("Tuple") && tupparams.size == vparams.size =>
+              Context.abort(
+                pretty"Wrong number of value arguments, given ${vparams.size}, but function expects a single tuple argument of type ${tuptpe}.\nHint: Try using `case (x, y) => ...` instead of `(x, y) => ...`.")
+          case _ => ()
+
+        vparams match {
+          // A *very* bad heuristic: we translate `case ... =>` into a match and bind it to a `__tmpRes`.
+          case source.ValueParam(source.IdDef("__tmpRes"), None) :: Nil =>
+            Context.abort(
+              pretty"Wrong number of value arguments, given a lambda `case`, but function expects ${vps.size} arguments.\nHint: Try using `(x, y) => ...` instead of `case (x, y) => ...`.")
+          case _ => ()
+        }
+
         Context.abort(s"Wrong number of value arguments, given ${vparams.size}, but function expects ${vps.size}.")
+      }

       if (bps.size != bparams.size)
         Context.abort(s"Wrong number of block arguments, given ${bparams.size}, but function expects ${bps.size}.")

... resulting in the following error messages:

Screenshot 2024-11-08 at 13 56 48 Screenshot 2024-11-08 at 13 57 00