@@ -977,7 +977,7 @@ object Typer extends Phase[NameResolved, Typechecked] {
977977 val bt @ FunctionType (tps, cps, vps, bps, tpe1, effs) = expected
978978
979979 // (2) Check wellformedness (that type, value, block params and args align)
980- assertArgsParamsAlign(" function " , tparams.size, vparams.size, bparams.size, tps.size, vps.size , bps.size )
980+ assertArgsParamsAlign(name = None , Aligned ( tparams, tps), Aligned (vparams, vps), Aligned (bparams , bps) )
981981
982982 // (3) Substitute type parameters
983983 val typeParams = tparams.map { p => p.symbol.asTypeParam }
@@ -1270,63 +1270,134 @@ object Typer extends Phase[NameResolved, Typechecked] {
12701270 }
12711271 }
12721272
1273+ /** Result of trying to align 'got' and 'expected' with matched pairs and mismatches */
1274+ case class Aligned [+ A , + B ](
1275+ matched : List [(A , B )], // / got and expected paired up
1276+ extra : List [A ], // / got but not expected
1277+ missing : List [B ] // / expected but not got
1278+ ) {
1279+ def isAligned : Boolean = extra.isEmpty && missing.isEmpty
1280+
1281+ def gotCount : Int = matched.size + extra.size
1282+ def expectedCount : Int = matched.size + missing.size
1283+ def delta : Int = extra.size - missing.size // > 0 => too many
1284+ }
1285+
1286+ object Aligned {
1287+ def apply [A , B ](got : List [A ], expected : List [B ]): Aligned [A , B ] = {
1288+ @ scala.annotation.tailrec
1289+ def loop (got : List [A ], expected : List [B ], matched : List [(A , B )]): Aligned [A , B ] =
1290+ (got, expected) match {
1291+ case (Nil , Nil ) => Aligned (matched.reverse, Nil , Nil )
1292+ case (extra, Nil ) => Aligned (matched.reverse, extra, Nil )
1293+ case (Nil , missing) => Aligned (matched.reverse, Nil , missing)
1294+ case (g :: gs, e :: es) => loop(gs, es, (g, e) :: matched)
1295+ }
1296+
1297+ loop(got, expected, Nil )
1298+ }
1299+ }
1300+
12731301 /**
12741302 * Asserts that number of {type, value, block} arguments is the same as
12751303 * the number of {type, value, block} parameters.
12761304 * If not, aborts the context with a nice error message.
1305+ * Also tries to add 'did you mean' context for the user on common errors.
1306+ *
1307+ * @param name None if it's a block literal, otherwise the expected name
12771308 */
12781309 private def assertArgsParamsAlign (
1279- name : String ,
1280- gotTypes : Int , gotValues : Int , gotBlocks : Int ,
1281- expectedTypes : Int , expectedValues : Int , expectedBlocks : Int
1282- )(using Context ): Unit = {
1310+ name : Option [String ],
1311+ types : Aligned [source.Id | ValueType , TypeParam ],
1312+ values : Aligned [source.ValueParam | source.ValueArg , ValueType ],
1313+ blocks : Aligned [source.BlockParam | source.Term , BlockType ]
1314+ )(using Context ): Unit = {
12831315
1284- val targsOk = gotTypes == 0 || gotTypes == expectedTypes
1285- val vargsOk = gotValues == expectedValues
1286- val bargsOk = gotBlocks == expectedBlocks
1316+ // Type args are OK iff nothing provided or perfectly aligned
1317+ val typesOk = types.gotCount == 0 || types.isAligned
12871318
12881319 def pluralized (n : Int , singular : String ): String =
12891320 if (n == 1 ) s " $n $singular" else s " $n ${singular}s "
12901321
1291- def formatArgs (types : Option [Int ], values : Option [Int ], blocks : Option [Int ]): String = {
1292- val parts = List (
1293- types.map { pluralized(_, " type argument" ) },
1294- values.map { pluralized(_, " value argument" ) },
1295- blocks.map { pluralized(_, " block argument" ) }
1296- ).flatten
1297-
1298- parts match {
1299- case Nil => " no arguments"
1300- case single :: Nil => single
1301- case init :+ last => init.mkString(" , " ) + " and " + last
1302- case _ => parts.mkString(" , " )
1322+ // Hint: Tuple vs arg-list confusion (also covers lambda case)
1323+ if (! values.isAligned) {
1324+ // 1. User wrote `case (x, y) => ...` but function expects multiple arguments
1325+ // => Did we match exactly 1 value param with `__arg... ` name, and have multiple args missing
1326+ // HACK: Hardcoded '__arg' to recognize lambda case
1327+ (values.matched, values.extra, values.missing) match {
1328+ case (List ((param : source.ValueParam , _)), Nil , _ :: _)
1329+ if param.id.name.startsWith(" __arg" ) =>
1330+ Context .info(pretty " Did you mean to use `(x, y) => ...` instead of `case (x, y) => ...`? " )
1331+ case _ => ()
13031332 }
1304- }
13051333
1306- val expected = formatArgs(
1307- Option .when(! targsOk) { expectedTypes },
1308- Option .when(! vargsOk) { expectedValues },
1309- Option .when(! bargsOk) { expectedBlocks }
1310- )
1311- val got = formatArgs(
1312- Option .when(! targsOk) { gotTypes },
1313- Option .when(! vargsOk) { gotValues },
1314- Option .when(! bargsOk) { gotBlocks }
1315- )
1334+ // 2a. User wrote `(x, y) => ... ` but function expects a single tuple
1335+ // 2b. User wrote `foo(x, y)` but the function expects a single tuple
1336+ // => Multiple extra value args, exactly 1 missing that's a tuple matching the count
1337+ // HACK: Hardcoded "tuple is a type whose name starts with 'Tuple'"
1338+ (values.matched, values.extra, values.missing) match {
1339+ case (List ((_, tupleTpe @ ValueTypeApp (TypeConstructor .Record (tupleName, _, _, _), args))), extras, Nil )
1340+ if tupleName.name.startsWith(" Tuple" ) && args.size == 1 + extras.size =>
1341+ name match {
1342+ case None => Context .info(pretty " Did you mean to use `case (x, y) => ...` to pattern match on the tuple ${tupleTpe}? " )
1343+ case Some (givenName) => Context .info(pretty " Did you mean to call ${givenName} with a single tuple argument instead of separate arguments? " )
1344+ }
1345+ case _ => ()
1346+ }
1347+
1348+ // 3. User wrote `foo((x, y))` (tuple literal) but function expects multiple arguments
1349+ // => Single matched arg that's a Call to a Tuple constructor, with some missing params
1350+ // HACK: Hardcoded "tuple constructor is IdRef to TupleN in effekt namespace"
1351+ (values.matched, values.extra, values.missing) match {
1352+ case (List ((arg : source.ValueArg , _)), Nil , missing@ (_ :: _)) =>
1353+ (arg.value, name) match {
1354+ case (source.Call (source.IdTarget (source.IdRef (List (" effekt" ), tupleName, _)), _, tupleArgs, _, _), Some (givenName))
1355+ if tupleName.startsWith(" Tuple" ) && tupleArgs.size == 1 + missing.size =>
1356+ Context .info(pretty " Did you mean to call ${givenName} with ${tupleArgs.size} separate arguments instead of a tuple? " )
1357+ case _ => ()
1358+ }
1359+ case _ => ()
1360+ }
1361+ }
13161362
1317- if (! vargsOk && ! bargsOk && gotValues + gotBlocks == expectedValues + expectedBlocks) {
1363+ // Hint: Value vs block argument confusion
1364+ if (! values.isAligned && ! blocks.isAligned && values.delta + blocks.delta == 0 ) {
13181365 // If total counts match, but individual do not, it's likely a value vs computation issue
1319- if (gotBlocks > expectedBlocks) {
1320- val diff = gotBlocks - expectedBlocks
1321- Context .info(pretty " Did you mean to pass ${pluralized(diff, " block argument" )} as a value? e.g. box it using `box { ... }` " )
1322- } else if (gotValues > expectedValues) {
1323- val diff = gotValues - expectedValues
1324- Context .info(pretty " Did you mean to pass ${pluralized(diff, " value argument" )} as a block (computation)? " )
1366+ if (blocks.delta > 0 ) {
1367+ Context .info(pretty " Did you mean to pass ${pluralized(blocks.delta, " block argument" )} as a value? e.g. box it using `box { ... }` " )
1368+ } else if (values.delta > 0 ) {
1369+ Context .info(pretty " Did you mean to pass ${pluralized(values.delta, " value argument" )} as a block (computation)? " )
13251370 }
13261371 }
13271372
1328- if (! targsOk || ! vargsOk || ! bargsOk) {
1329- Context .abort(s " Wrong number of arguments to ${name}: expected ${expected}, but got ${got}" )
1373+ if (! typesOk || ! values.isAligned || ! blocks.isAligned) {
1374+ def formatArgs (types : Option [Int ], values : Option [Int ], blocks : Option [Int ]): String = {
1375+ val parts = List (
1376+ types.map { pluralized(_, " type argument" ) },
1377+ values.map { pluralized(_, " value argument" ) },
1378+ blocks.map { pluralized(_, " block argument" ) }
1379+ ).flatten
1380+
1381+ parts match {
1382+ case Nil => " no arguments"
1383+ case single :: Nil => single
1384+ case init :+ last => init.mkString(" , " ) + " and " + last
1385+ case _ => parts.mkString(" , " )
1386+ }
1387+ }
1388+
1389+ val expected = formatArgs(
1390+ Option .when(! typesOk) { types.expectedCount },
1391+ Option .when(! values.isAligned) { values.expectedCount },
1392+ Option .when(! blocks.isAligned) { blocks.expectedCount }
1393+ )
1394+ val got = formatArgs(
1395+ Option .when(! typesOk) { types.gotCount },
1396+ Option .when(! values.isAligned) { values.gotCount },
1397+ Option .when(! blocks.isAligned) { blocks.gotCount }
1398+ )
1399+
1400+ Context .abort(s " Wrong number of arguments to ${name getOrElse " function" }: expected ${expected}, but got ${got}" )
13301401 }
13311402 }
13321403
@@ -1343,7 +1414,7 @@ object Typer extends Phase[NameResolved, Typechecked] {
13431414 val callsite = currentCapture
13441415
13451416 // (0) Check that arg & param counts align
1346- assertArgsParamsAlign(name, targs.size, vargs.size, bargs.size , funTpe.tparams.size, funTpe.vparams.size, funTpe.bparams.size )
1417+ assertArgsParamsAlign(name = Some (name), Aligned (targs , funTpe.tparams), Aligned (vargs, funTpe.vparams), Aligned (bargs, funTpe.bparams) )
13471418
13481419 // (1) Instantiate blocktype
13491420 // e.g. `[A, B] (A, A) => B` becomes `(?A, ?A) => ?B`
0 commit comments