From ddc4cd0c47dec90e3c5b9e1cc7c6ba4f947f89d9 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 8 Aug 2023 18:19:41 +0200 Subject: [PATCH 1/2] Issue "positional after named argument" errors Issue "positional after named argument" errors if a positional argument follows a named argument and one of the following is true: - There is a formal argument before the argument position that has not yet been instantiated with a previous actual argument, (either named or positional), or - The formal parameter at the argument position is also mentioned in a subsequent named parameter. This brings the behavior largely in line with Scala 2 --- .../dotty/tools/dotc/typer/Applications.scala | 35 +++++++++---- tests/neg/i18122.check | 52 +++++++++++++++++++ tests/neg/i18122.scala | 49 +++++++++++++++++ 3 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 tests/neg/i18122.check create mode 100644 tests/neg/i18122.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 81d7ad23913b..5fea800946ee 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -503,7 +503,21 @@ trait Applications extends Compatibility { def infoStr = if methType.isErroneous then "" else i": $methType" i"${err.refStr(methRef)}$infoStr" - /** Re-order arguments to correctly align named arguments */ + /** Re-order arguments to correctly align named arguments + * Issue errors in the following situations: + * + * - "positional after named argument" if a positional argument follows a named + * argument and one of the following is true: + * + * - There is a formal argument before the argument position + * that has not yet been instantiated with a previous actual argument, + * (either named or positional), or + * - The formal parameter at the argument position is also mentioned + * in a subsequent named parameter. + * - "parameter already instantiated" if a two named arguments have the same name. + * - "does not have parameter" if a named parameter does not mention a formal + * parameter name. + */ def reorder[T <: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = { /** @param pnames The list of parameter names that are missing arguments @@ -517,18 +531,19 @@ trait Applications extends Compatibility { * 2. For every `(name -> arg)` in `nameToArg`, `arg` is an element of `args` */ def handleNamed(pnames: List[Name], args: List[Trees.Tree[T]], - nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name]): List[Trees.Tree[T]] = pnames match { + nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name], + missingArgs: Boolean): List[Trees.Tree[T]] = pnames match { case pname :: pnames1 if nameToArg contains pname => // there is a named argument for this parameter; pick it - nameToArg(pname) :: handleNamed(pnames1, args, nameToArg - pname, toDrop + pname) + nameToArg(pname) :: handleNamed(pnames1, args, nameToArg - pname, toDrop + pname, missingArgs) case _ => def pnamesRest = if (pnames.isEmpty) pnames else pnames.tail args match { case (arg @ NamedArg(aname, _)) :: args1 => if (toDrop contains aname) // argument is already passed - handleNamed(pnames, args1, nameToArg, toDrop - aname) + handleNamed(pnames, args1, nameToArg, toDrop - aname, missingArgs) else if ((nameToArg contains aname) && pnames.nonEmpty) // argument is missing, pass an empty tree - genericEmptyTree :: handleNamed(pnames.tail, args, nameToArg, toDrop) + genericEmptyTree :: handleNamed(pnames.tail, args, nameToArg, toDrop, missingArgs = true) else { // name not (or no longer) available for named arg def msg = if (methodType.paramNames contains aname) @@ -536,13 +551,15 @@ trait Applications extends Compatibility { else em"$methString does not have a parameter $aname" fail(msg, arg.asInstanceOf[Arg]) - arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop) + arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop, missingArgs) } case arg :: args1 => - arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop) // unnamed argument; pick it + if toDrop.nonEmpty || missingArgs then + report.error(i"positional after named argument", arg.srcPos) + arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop, missingArgs) // unnamed argument; pick it case Nil => // no more args, continue to pick up any preceding named args if (pnames.isEmpty) Nil - else handleNamed(pnamesRest, args, nameToArg, toDrop) + else handleNamed(pnamesRest, args, nameToArg, toDrop, missingArgs) } } @@ -550,7 +567,7 @@ trait Applications extends Compatibility { args match { case (arg: NamedArg @unchecked) :: _ => val nameAssocs = for (case arg @ NamedArg(name, _) <- args) yield (name, arg) - handleNamed(pnames, args, nameAssocs.toMap, Set()) + handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set(), missingArgs = false) case arg :: args1 => arg :: handlePositional(if (pnames.isEmpty) Nil else pnames.tail, args1) case Nil => Nil diff --git a/tests/neg/i18122.check b/tests/neg/i18122.check new file mode 100644 index 000000000000..0d08dc33c52a --- /dev/null +++ b/tests/neg/i18122.check @@ -0,0 +1,52 @@ +-- Error: tests/neg/i18122.scala:10:16 --------------------------------------------------------------------------------- +10 | foo1(y = 1, 2, x = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:11:16 --------------------------------------------------------------------------------- +11 | foo2(y = 1, 2, x = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:12:16 --------------------------------------------------------------------------------- +12 | foo1(y = 1, 2, z = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:13:16 --------------------------------------------------------------------------------- +13 | foo2(y = 1, 2, z = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:14:16 --------------------------------------------------------------------------------- +14 | foo1(y = 1, 2) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:15:16 --------------------------------------------------------------------------------- +15 | foo2(y = 1, 2) // error: positional after named + | ^ + | positional after named argument +-- [E171] Type Error: tests/neg/i18122.scala:17:8 ---------------------------------------------------------------------- +17 | bar1() // error: missing arg + | ^^^^^^ + | missing argument for parameter x of method bar1 in object Test: (x: Int, ys: Int*): Unit +-- [E171] Type Error: tests/neg/i18122.scala:23:8 ---------------------------------------------------------------------- +23 | bar1(ys = 1) // error: missing arg + | ^^^^^^^^^^^^ + | missing argument for parameter x of method bar1 in object Test: (x: Int, ys: Int*): Unit +-- Error: tests/neg/i18122.scala:43:16 --------------------------------------------------------------------------------- +43 | bar1(x = 1, 2, ys = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:44:18 --------------------------------------------------------------------------------- +44 | bar1(1, 2, ys = 3) // error: parameter ys is already instantiated + | ^^^^^^ + | parameter ys of method bar1 in object Test: (x: Int, ys: Int*): Unit is already instantiated +-- Error: tests/neg/i18122.scala:45:16 --------------------------------------------------------------------------------- +45 | bar2(x = 1, 2, ys = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:46:17 --------------------------------------------------------------------------------- +46 | bar1(ys = 1, 2, x = 3) // error: positional after named + | ^ + | positional after named argument +-- Error: tests/neg/i18122.scala:47:17 --------------------------------------------------------------------------------- +47 | bar2(ys = 1, 2, x = 3) // error: positional after named + | ^ + | positional after named argument diff --git a/tests/neg/i18122.scala b/tests/neg/i18122.scala new file mode 100644 index 000000000000..ceb6275af333 --- /dev/null +++ b/tests/neg/i18122.scala @@ -0,0 +1,49 @@ +object Test { + def foo1(x: Int, y: Int, z: Int) = println((x, y, z)) + def foo2(x: Int = 0, y: Int, z: Int) = println((x, y, z)) + def bar1(x: Int, ys: Int*) = println((x, ys)) + def bar2(x: Int = 0, ys: Int*) = println((x, ys)) + + def main(args: Array[String]) = { + foo1(1, y = 2, 3) + foo2(1, y = 2, 3) + foo1(y = 1, 2, x = 3) // error: positional after named + foo2(y = 1, 2, x = 3) // error: positional after named + foo1(y = 1, 2, z = 3) // error: positional after named + foo2(y = 1, 2, z = 3) // error: positional after named + foo1(y = 1, 2) // error: positional after named + foo2(y = 1, 2) // error: positional after named + + bar1() // error: missing arg + bar2() + bar1(1) + bar2(1) + bar1(x = 1) + bar2(x = 1) + bar1(ys = 1) // error: missing arg + bar2(ys = 1) + bar1(1, 2) + bar2(1, 2) + bar1(1, ys = 2) + bar2(1, ys = 2) + bar1(x = 1, 2) + bar2(x = 1, 2) + bar1(x = 1, ys = 2) + bar2(x = 1, ys = 2) + bar1(ys = 1, x = 2) + bar2(ys = 1, x = 2) + bar1(1, 2, 3) + bar2(1, 2, 3) + bar1(1, ys = 2, 3) + bar2(1, ys = 2, 3) + bar1(x = 1, 2, 3) + bar2(x = 1, 2, 3) + bar1(x = 1, ys = 2, 3) + bar2(x = 1, ys = 2, 3) + bar1(x = 1, 2, ys = 3) // error: positional after named + bar1(1, 2, ys = 3) // error: parameter ys is already instantiated + bar2(x = 1, 2, ys = 3) // error: positional after named + bar1(ys = 1, 2, x = 3) // error: positional after named + bar2(ys = 1, 2, x = 3) // error: positional after named + } +} \ No newline at end of file From 91e56de9145c2b76b0c5b51ff47be81365aa4ccd Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 8 Aug 2023 21:47:37 +0200 Subject: [PATCH 2/2] Fix test --- tests/pos-with-compiler-cc/dotc/transform/PickleQuotes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos-with-compiler-cc/dotc/transform/PickleQuotes.scala b/tests/pos-with-compiler-cc/dotc/transform/PickleQuotes.scala index f3ae6a377aab..b27bbdc0fccd 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/PickleQuotes.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/PickleQuotes.scala @@ -134,7 +134,7 @@ class PickleQuotes extends MacroTransform { contents += content val holeType = if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe) - val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType)) + val hole = cpy.Hole(tree)(content = EmptyTree, tpt = TypeTree(holeType)) if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole case tree: DefTree => val newAnnotations = tree.symbol.annotations.mapconserve { annot =>