From 55455ed838e040143fa64edf15574627f4275857 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 13 Sep 2024 09:46:32 -0700 Subject: [PATCH 1/5] Cleanup of reorder --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../dotty/tools/dotc/typer/Applications.scala | 66 ++++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b88a3e6e986a..df2d73084b89 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1033,6 +1033,7 @@ class Definitions { @tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated") @tu lazy val DeprecatedOverridingAnnot: ClassSymbol = requiredClass("scala.deprecatedOverriding") @tu lazy val DeprecatedInheritanceAnnot: ClassSymbol = requiredClass("scala.deprecatedInheritance") + @tu lazy val DeprecatedNameAnnot: ClassSymbol = requiredClass("scala.deprecatedName") @tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous") @tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound") @tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam") diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 71fc250d0710..1485f643f8ac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -631,12 +631,17 @@ trait Applications extends Compatibility { * - "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]] = { + def reorder[T <: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = - /** @param pnames The list of parameter names that are missing arguments + inline def tailOf[A](list: List[A]): List[A] = if list.isEmpty then list else list.tail // list.drop(1) + + /** Reorder the suffix of named args per a list of required names. + * + * @param pnames The list of parameter names that are missing arguments * @param args The list of arguments that are not yet passed, or that are waiting to be dropped * @param nameToArg A map from as yet unseen names to named arguments - * @param toDrop A set of names that have already be passed as named arguments + * @param toDrop A set of names that have already been passed as named arguments + * @param missingArgs true if args were already missing, so error on positional * * For a well-typed application we have the invariants * @@ -645,49 +650,46 @@ trait Applications extends Compatibility { */ def handleNamed(pnames: List[Name], args: List[Trees.Tree[T]], nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name], - missingArgs: Boolean): List[Trees.Tree[T]] = pnames match { - case pname :: pnames1 if nameToArg contains pname => + missingArgs: Boolean): List[Trees.Tree[T]] = pnames match + case pname :: pnames if nameToArg.contains(pname) => // there is a named argument for this parameter; pick it - nameToArg(pname) :: handleNamed(pnames1, args, nameToArg - pname, toDrop + pname, missingArgs) + nameToArg(pname) :: handleNamed(pnames, args, nameToArg - pname, toDrop + pname, missingArgs) case _ => - def pnamesRest = if (pnames.isEmpty) pnames else pnames.tail - args match { + args match case (arg @ NamedArg(aname, _)) :: args1 => - if (toDrop contains aname) // argument is already passed - 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, missingArgs = true) - else { // name not (or no longer) available for named arg + if toDrop.contains(aname) // named argument is already passed + then handleNamed(pnames, args1, nameToArg, toDrop - aname, missingArgs) + else if nameToArg.contains(aname) && pnames.nonEmpty // argument is missing, pass an empty tree + then 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) + if methodType.paramNames.contains(aname) then em"parameter $aname of $methString is already instantiated" else em"$methString does not have a parameter $aname" fail(msg, arg.asInstanceOf[Arg]) - arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop, missingArgs) - } - case arg :: args1 => + arg :: handleNamed(tailOf(pnames), args1, nameToArg, toDrop, missingArgs) + case arg :: args => 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, missingArgs) - } - } + arg :: handleNamed(tailOf(pnames), args, nameToArg, toDrop, missingArgs) // unnamed argument; pick it + case nil => // no more args, continue to pick up any preceding named args + if pnames.isEmpty then nil + else handleNamed(tailOf(pnames), nil, nameToArg, toDrop, missingArgs) + /** Skip prefix of positional args, then handleNamed */ def handlePositional(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = - args match { - case (arg: NamedArg @unchecked) :: _ => - val nameAssocs = for (case arg @ NamedArg(name, _) <- args) yield (name, arg) - 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 - } + args match + case (_: NamedArg) :: _ => + //val nameAssocs = for case arg @ NamedArg(name, _) <- args yield (name, arg) + val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg } + handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false) + case arg :: args => + arg :: handlePositional(tailOf(pnames), args) + case nil => nil handlePositional(methodType.paramNames, args) - } + end reorder /** Is `sym` a constructor of a Java-defined annotation? */ def isJavaAnnotConstr(sym: Symbol): Boolean = From bc2ad1a527bc0bdaa04fc36cd7a6711dcfbbda8e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 12 Sep 2024 19:29:51 -0700 Subject: [PATCH 2/5] Named arg may be deprecatedName --- .../dotty/tools/dotc/typer/Applications.scala | 46 ++++++++++++++----- tests/neg/i18122.check | 12 ++--- tests/warn/i19077.check | 24 ++++++++++ tests/warn/i19077.scala | 11 +++++ 4 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 tests/warn/i19077.check create mode 100644 tests/warn/i19077.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1485f643f8ac..b268c24ade89 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -635,6 +635,21 @@ trait Applications extends Compatibility { inline def tailOf[A](list: List[A]): List[A] = if list.isEmpty then list else list.tail // list.drop(1) + def hasDeprecatedName(pname: Name, other: Name, t: Trees.Tree[T]): Boolean = !ctx.isAfterTyper && + methRef.symbol.paramSymss.flatten.find(_.name == pname).flatMap(_.getAnnotation(defn.DeprecatedNameAnnot)).match + case Some(annot) => + val name = annot.argumentConstantString(0) + val version = annot.argumentConstantString(1).filter(!_.isEmpty) + val since = version.map(v => s" (since $v)").getOrElse("") + name.map(_.toTermName) match + case Some(`other`) => + report.deprecationWarning(em"the parameter name $other is deprecated$since: use $pname instead", t.srcPos) + case Some(`pname`) | None => + report.deprecationWarning(em"naming parameter $pname is deprecated$since", t.srcPos) + case _ => + true + case _ => false + /** Reorder the suffix of named args per a list of required names. * * @param pnames The list of parameter names that are missing arguments @@ -650,17 +665,24 @@ trait Applications extends Compatibility { */ def handleNamed(pnames: List[Name], args: List[Trees.Tree[T]], nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name], - missingArgs: Boolean): List[Trees.Tree[T]] = pnames match - case pname :: pnames if nameToArg.contains(pname) => - // there is a named argument for this parameter; pick it - nameToArg(pname) :: handleNamed(pnames, args, nameToArg - pname, toDrop + pname, missingArgs) + missingArgs: Boolean): List[Trees.Tree[T]] = + pnames match + case pname :: pnames if nameToArg.contains(pname) => // use the named argument for this parameter + val arg = nameToArg(pname) + hasDeprecatedName(pname, nme.NO_NAME, arg) + arg :: handleNamed(pnames, args, nameToArg - pname, toDrop + pname, missingArgs) case _ => args match - case (arg @ NamedArg(aname, _)) :: args1 => - if toDrop.contains(aname) // named argument is already passed - then handleNamed(pnames, args1, nameToArg, toDrop - aname, missingArgs) - else if nameToArg.contains(aname) && pnames.nonEmpty // argument is missing, pass an empty tree - then genericEmptyTree :: handleNamed(pnames.tail, args, nameToArg, toDrop, missingArgs = true) + case allArgs @ (arg @ NamedArg(aname, _)) :: args => + if toDrop.contains(aname) then // named argument was already picked + handleNamed(pnames, args, nameToArg, toDrop - aname, missingArgs) + else if pnames.nonEmpty && nameToArg.contains(aname) then + val pname = pnames.head + if hasDeprecatedName(pname, aname, arg) then // name was deprecated alt, so try again with canonical name + val parg = cpy.NamedArg(arg)(pname, arg.arg).asInstanceOf[Trees.NamedArg[T]] + handleNamed(pnames, parg :: args, nameToArg.removed(aname).updated(pname, parg), toDrop, missingArgs) + else // argument for pname is missing, pass an empty tree + genericEmptyTree :: handleNamed(pnames.tail, allArgs, nameToArg, toDrop, missingArgs = true) else // name not (or no longer) available for named arg def msg = if methodType.paramNames.contains(aname) then @@ -668,7 +690,7 @@ trait Applications extends Compatibility { else em"$methString does not have a parameter $aname" fail(msg, arg.asInstanceOf[Arg]) - arg :: handleNamed(tailOf(pnames), args1, nameToArg, toDrop, missingArgs) + arg :: handleNamed(tailOf(pnames), args, nameToArg, toDrop, missingArgs) case arg :: args => if toDrop.nonEmpty || missingArgs then report.error(i"positional after named argument", arg.srcPos) @@ -680,8 +702,10 @@ trait Applications extends Compatibility { /** Skip prefix of positional args, then handleNamed */ def handlePositional(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = args match + case (arg @ NamedArg(name, _)) :: args if !pnames.isEmpty && pnames.head == name => + hasDeprecatedName(name, nme.NO_NAME, arg) + arg :: handlePositional(pnames.tail, args) case (_: NamedArg) :: _ => - //val nameAssocs = for case arg @ NamedArg(name, _) <- args yield (name, arg) val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg } handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false) case arg :: args => diff --git a/tests/neg/i18122.check b/tests/neg/i18122.check index 0d08dc33c52a..22d9f2427df0 100644 --- a/tests/neg/i18122.check +++ b/tests/neg/i18122.check @@ -30,18 +30,18 @@ 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 --------------------------------------------------------------------------------- +-- Error: tests/neg/i18122.scala:43:22 --------------------------------------------------------------------------------- 43 | bar1(x = 1, 2, ys = 3) // error: positional after named - | ^ - | positional after named argument + | ^^^^^^ + | parameter ys of method bar1 in object Test: (x: Int, ys: Int*): Unit is already instantiated -- 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 --------------------------------------------------------------------------------- +-- Error: tests/neg/i18122.scala:45:22 --------------------------------------------------------------------------------- 45 | bar2(x = 1, 2, ys = 3) // error: positional after named - | ^ - | positional after named argument + | ^^^^^^ + | parameter ys of method bar2 in object Test: (x: Int, ys: Int*): Unit is already instantiated -- Error: tests/neg/i18122.scala:46:17 --------------------------------------------------------------------------------- 46 | bar1(ys = 1, 2, x = 3) // error: positional after named | ^ diff --git a/tests/warn/i19077.check b/tests/warn/i19077.check new file mode 100644 index 000000000000..9a97b9826674 --- /dev/null +++ b/tests/warn/i19077.check @@ -0,0 +1,24 @@ +-- Deprecation Warning: tests/warn/i19077.scala:8:6 -------------------------------------------------------------------- +8 | f(x = 1, 2, 3) // warn + | ^^^^^ + | naming parameter x is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:9:9 -------------------------------------------------------------------- +9 | f(1, y = 2, 3) // warn + | ^^^^^ + | naming parameter y is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:10:12 ------------------------------------------------------------------ +10 | f(1, 2, w = 3) // warn + | ^^^^^ + | the parameter name w is deprecated: use z instead +-- Deprecation Warning: tests/warn/i19077.scala:11:13 ------------------------------------------------------------------ +11 | f(w = 3, x = 1, y = 2) // warn // warn // warn + | ^^^^^ + | naming parameter x is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:11:20 ------------------------------------------------------------------ +11 | f(w = 3, x = 1, y = 2) // warn // warn // warn + | ^^^^^ + | naming parameter y is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:11:6 ------------------------------------------------------------------- +11 | f(w = 3, x = 1, y = 2) // warn // warn // warn + | ^^^^^ + | the parameter name w is deprecated: use z instead diff --git a/tests/warn/i19077.scala b/tests/warn/i19077.scala new file mode 100644 index 000000000000..91842f97a724 --- /dev/null +++ b/tests/warn/i19077.scala @@ -0,0 +1,11 @@ +//> using options -deprecation + +def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") z: Int) = x+y+z + +@main def Test = + f(1, 2, 3) // nowarn + f(1, 2, z = 3) // nowarn + f(x = 1, 2, 3) // warn + f(1, y = 2, 3) // warn + f(1, 2, w = 3) // warn + f(w = 3, x = 1, y = 2) // warn // warn // warn From 22f7cfb18ee405415a6be714c0d3d74a577862ab Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sun, 16 Feb 2025 18:30:08 -0800 Subject: [PATCH 3/5] Clean up and correct --- .../dotty/tools/dotc/typer/Applications.scala | 145 ++++++++++-------- tests/neg/i19077.check | 17 ++ tests/neg/i19077.scala | 8 + tests/warn/i19077.check | 52 +++++-- tests/warn/i19077.scala | 15 ++ 5 files changed, 160 insertions(+), 77 deletions(-) create mode 100644 tests/neg/i19077.check create mode 100644 tests/neg/i19077.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index b268c24ade89..fe4d3d6cfe31 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -616,6 +616,8 @@ trait Applications extends Compatibility { def infoStr = if methType.isErroneous then "" else i": $methType" i"${err.refStr(methRef)}$infoStr" + private type TreeList[T <: Untyped] = List[Trees.Tree[T]] + /** Re-order arguments to correctly align named arguments * Issue errors in the following situations: * @@ -627,28 +629,43 @@ trait Applications extends Compatibility { * (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. + * - "parameter already instantiated" if two named arguments have the same name or deprecated alias. * - "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]] = - - inline def tailOf[A](list: List[A]): List[A] = if list.isEmpty then list else list.tail // list.drop(1) - - def hasDeprecatedName(pname: Name, other: Name, t: Trees.Tree[T]): Boolean = !ctx.isAfterTyper && - methRef.symbol.paramSymss.flatten.find(_.name == pname).flatMap(_.getAnnotation(defn.DeprecatedNameAnnot)).match - case Some(annot) => - val name = annot.argumentConstantString(0) - val version = annot.argumentConstantString(1).filter(!_.isEmpty) - val since = version.map(v => s" (since $v)").getOrElse("") - name.map(_.toTermName) match - case Some(`other`) => - report.deprecationWarning(em"the parameter name $other is deprecated$since: use $pname instead", t.srcPos) - case Some(`pname`) | None => - report.deprecationWarning(em"naming parameter $pname is deprecated$since", t.srcPos) - case _ => - true - case _ => false + def reorder[T <: Untyped](args: TreeList[T]): TreeList[T] = + + extension [A](list: List[A]) inline def dropOne = if list.isEmpty then list else list.tail // aka list.drop(1) + + extension (dna: Annotation) + def deprecatedName: Name = + dna.argumentConstantString(0).map(_.toTermName).getOrElse(nme.NO_NAME) + def since: String = + val version = dna.argumentConstantString(1).filter(!_.isEmpty) + version.map(v => s" (since $v)").getOrElse("") + + val deprecatedNames: Map[Name, Annotation] = + methRef.symbol.paramSymss.find(_.exists(_.isTerm)) match + case Some(ps) if ps.exists(_.hasAnnotation(defn.DeprecatedNameAnnot)) => + ps.flatMap: p => + p.getAnnotation(defn.DeprecatedNameAnnot).map(p.name -> _) + .toMap + case _ => Map.empty + + extension (name: Name) + def isMatchedBy(usage: Name): Boolean = + name == usage + || deprecatedNames.get(name).exists(_.deprecatedName == usage) + def checkDeprecationOf(usage: Name, pos: SrcPos): Unit = if !ctx.isAfterTyper then + for dna <- deprecatedNames.get(name) do + dna.deprecatedName match + case `name` | nme.NO_NAME if name == usage => + report.deprecationWarning(em"naming parameter $usage is deprecated${dna.since}", pos) + case `usage` => + report.deprecationWarning(em"the parameter name $usage is deprecated${dna.since}: use $name instead", pos) + case _ => + def alternative: Name = + deprecatedNames.get(name).map(_.deprecatedName).getOrElse(nme.NO_NAME) /** Reorder the suffix of named args per a list of required names. * @@ -662,55 +679,61 @@ trait Applications extends Compatibility { * * 1. `(args diff toDrop)` can be reordered to match `pnames` * 2. For every `(name -> arg)` in `nameToArg`, `arg` is an element of `args` + * + * Recurse over the parameter names looking for named args to use. + * If there are no more parameters or no args fit, process the next arg: + * a named arg may be previously used, or not yet used, or badly named. */ - def handleNamed(pnames: List[Name], args: List[Trees.Tree[T]], + def handleNamed(pnames: List[Name], args: TreeList[T], nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name], - missingArgs: Boolean): List[Trees.Tree[T]] = + missingArgs: Boolean): TreeList[T] = pnames match - case pname :: pnames if nameToArg.contains(pname) => // use the named argument for this parameter - val arg = nameToArg(pname) - hasDeprecatedName(pname, nme.NO_NAME, arg) + case pname :: pnames if nameToArg.contains(pname) => + val arg = nameToArg(pname) // use the named argument for this parameter + pname.checkDeprecationOf(pname, arg.srcPos) arg :: handleNamed(pnames, args, nameToArg - pname, toDrop + pname, missingArgs) + case pname :: pnames if nameToArg.contains(pname.alternative) => + val alt = pname.alternative + val arg = nameToArg(alt) // use the named argument for this parameter + pname.checkDeprecationOf(alt, arg.srcPos) + arg :: handleNamed(pnames, args, nameToArg - alt, toDrop + alt, missingArgs) case _ => args match - case allArgs @ (arg @ NamedArg(aname, _)) :: args => - if toDrop.contains(aname) then // named argument was already picked - handleNamed(pnames, args, nameToArg, toDrop - aname, missingArgs) - else if pnames.nonEmpty && nameToArg.contains(aname) then - val pname = pnames.head - if hasDeprecatedName(pname, aname, arg) then // name was deprecated alt, so try again with canonical name - val parg = cpy.NamedArg(arg)(pname, arg.arg).asInstanceOf[Trees.NamedArg[T]] - handleNamed(pnames, parg :: args, nameToArg.removed(aname).updated(pname, parg), toDrop, missingArgs) - else // argument for pname is missing, pass an empty tree - genericEmptyTree :: handleNamed(pnames.tail, allArgs, nameToArg, toDrop, missingArgs = true) - else // name not (or no longer) available for named arg - def msg = - if methodType.paramNames.contains(aname) then - em"parameter $aname of $methString is already instantiated" - else - em"$methString does not have a parameter $aname" - fail(msg, arg.asInstanceOf[Arg]) - arg :: handleNamed(tailOf(pnames), args, nameToArg, toDrop, missingArgs) - case arg :: args => - if toDrop.nonEmpty || missingArgs then - report.error(i"positional after named argument", arg.srcPos) - arg :: handleNamed(tailOf(pnames), args, nameToArg, toDrop, missingArgs) // unnamed argument; pick it - case nil => // no more args, continue to pick up any preceding named args - if pnames.isEmpty then nil - else handleNamed(tailOf(pnames), nil, nameToArg, toDrop, missingArgs) - - /** Skip prefix of positional args, then handleNamed */ - def handlePositional(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = - args match - case (arg @ NamedArg(name, _)) :: args if !pnames.isEmpty && pnames.head == name => - hasDeprecatedName(name, nme.NO_NAME, arg) - arg :: handlePositional(pnames.tail, args) - case (_: NamedArg) :: _ => - val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg } - handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false) + case allArgs @ (arg @ NamedArg(aname, _)) :: args => + if toDrop.contains(aname) then + // named argument was already picked (using aname), skip it + handleNamed(pnames, args, nameToArg, toDrop - aname, missingArgs) + else if pnames.nonEmpty && nameToArg.contains(aname) then + // argument for pname is missing, pass an empty tree; arg may be used later, so keep it + genericEmptyTree :: handleNamed(pnames.tail, allArgs, nameToArg, toDrop, missingArgs = true) + else // name not (or no longer) available for named arg + def msg = + if methodType.paramNames.exists(nm => nm == aname || nm.alternative == aname) then + em"parameter $aname of $methString is already instantiated" + else + em"$methString does not have a parameter $aname" + fail(msg, arg.asInstanceOf[Arg]) + arg :: handleNamed(pnames.dropOne, args, nameToArg, toDrop, missingArgs) case arg :: args => - arg :: handlePositional(tailOf(pnames), args) - case nil => nil + if toDrop.nonEmpty || missingArgs then + report.error(i"positional after named argument", arg.srcPos) + arg :: handleNamed(pnames.dropOne, args, nameToArg, toDrop, missingArgs) // unnamed argument; pick it + case nil => // no more args, continue to pick up any preceding named args + if pnames.isEmpty then nil + else handleNamed(pnames.dropOne, args = nil, nameToArg, toDrop, missingArgs) + + // Skip prefix of positional args, then handleNamed + def handlePositional(pnames: List[Name], args: TreeList[T]): TreeList[T] = + args match + case (arg @ NamedArg(name, _)) :: args if !pnames.isEmpty && pnames.head.isMatchedBy(name) => + pnames.head.checkDeprecationOf(name, arg.srcPos) + arg :: handlePositional(pnames.tail, args) + case (_: NamedArg) :: _ => + val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg } + handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false) + case arg :: args => + arg :: handlePositional(pnames.dropOne, args) + case nil => nil handlePositional(methodType.paramNames, args) end reorder diff --git a/tests/neg/i19077.check b/tests/neg/i19077.check new file mode 100644 index 000000000000..4a95821a1c82 --- /dev/null +++ b/tests/neg/i19077.check @@ -0,0 +1,17 @@ +-- Error: tests/neg/i19077.scala:5:15 ---------------------------------------------------------------------------------- +5 | f(1, 2, 3, x = 42) // error + | ^^^^^^ + | parameter x of method f: (x: Int, y: Int, z: Int): Int is already instantiated +-- Error: tests/neg/i19077.scala:6:15 ---------------------------------------------------------------------------------- +6 | f(1, 2, 3, w = 42) // error + | ^^^^^^ + | parameter w of method f: (x: Int, y: Int, z: Int): Int is already instantiated +-- Error: tests/neg/i19077.scala:7:20 ---------------------------------------------------------------------------------- +7 | f(1, 2, w = 42, z = 27) // error + | ^^^^^^ + | parameter z of method f: (x: Int, y: Int, z: Int): Int is already instantiated +-- Error: tests/neg/i19077.scala:8:20 ---------------------------------------------------------------------------------- +8 | f(1, 2, z = 42, w = 27) // error + | ^^^^^^ + | parameter w of method f: (x: Int, y: Int, z: Int): Int is already instantiated +there was 1 deprecation warning; re-run with -deprecation for details diff --git a/tests/neg/i19077.scala b/tests/neg/i19077.scala new file mode 100644 index 000000000000..26cddadbb83f --- /dev/null +++ b/tests/neg/i19077.scala @@ -0,0 +1,8 @@ + +def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") z: Int) = x+y+z + +@main def Test = + f(1, 2, 3, x = 42) // error + f(1, 2, 3, w = 42) // error + f(1, 2, w = 42, z = 27) // error + f(1, 2, z = 42, w = 27) // error diff --git a/tests/warn/i19077.check b/tests/warn/i19077.check index 9a97b9826674..646e529c38f3 100644 --- a/tests/warn/i19077.check +++ b/tests/warn/i19077.check @@ -1,24 +1,44 @@ --- Deprecation Warning: tests/warn/i19077.scala:8:6 -------------------------------------------------------------------- -8 | f(x = 1, 2, 3) // warn - | ^^^^^ - | naming parameter x is deprecated --- Deprecation Warning: tests/warn/i19077.scala:9:9 -------------------------------------------------------------------- -9 | f(1, y = 2, 3) // warn - | ^^^^^ - | naming parameter y is deprecated --- Deprecation Warning: tests/warn/i19077.scala:10:12 ------------------------------------------------------------------ -10 | f(1, 2, w = 3) // warn +-- Deprecation Warning: tests/warn/i19077.scala:26:14 ------------------------------------------------------------------ +26 | def g = f(y = 42) // warn but omit empty since + | ^^^^^^ + | the parameter name y is deprecated: use x instead +-- Deprecation Warning: tests/warn/i19077.scala:12:6 ------------------------------------------------------------------- +12 | f(x = 1, 2, 3) // warn + | ^^^^^ + | naming parameter x is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:13:9 ------------------------------------------------------------------- +13 | f(1, y = 2, 3) // warn + | ^^^^^ + | naming parameter y is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:14:12 ------------------------------------------------------------------ +14 | f(1, 2, w = 3) // warn | ^^^^^ | the parameter name w is deprecated: use z instead --- Deprecation Warning: tests/warn/i19077.scala:11:13 ------------------------------------------------------------------ -11 | f(w = 3, x = 1, y = 2) // warn // warn // warn +-- Deprecation Warning: tests/warn/i19077.scala:15:6 ------------------------------------------------------------------- +15 | f(x = 1, w = 3, y = 2) // warn // warn // warn + | ^^^^^ + | naming parameter x is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:15:20 ------------------------------------------------------------------ +15 | f(x = 1, w = 3, y = 2) // warn // warn // warn + | ^^^^^ + | naming parameter y is deprecated +-- Deprecation Warning: tests/warn/i19077.scala:15:13 ------------------------------------------------------------------ +15 | f(x = 1, w = 3, y = 2) // warn // warn // warn + | ^^^^^ + | the parameter name w is deprecated: use z instead +-- Deprecation Warning: tests/warn/i19077.scala:16:13 ------------------------------------------------------------------ +16 | f(w = 3, x = 1, y = 2) // warn // warn // warn | ^^^^^ | naming parameter x is deprecated --- Deprecation Warning: tests/warn/i19077.scala:11:20 ------------------------------------------------------------------ -11 | f(w = 3, x = 1, y = 2) // warn // warn // warn +-- Deprecation Warning: tests/warn/i19077.scala:16:20 ------------------------------------------------------------------ +16 | f(w = 3, x = 1, y = 2) // warn // warn // warn | ^^^^^ | naming parameter y is deprecated --- Deprecation Warning: tests/warn/i19077.scala:11:6 ------------------------------------------------------------------- -11 | f(w = 3, x = 1, y = 2) // warn // warn // warn +-- Deprecation Warning: tests/warn/i19077.scala:16:6 ------------------------------------------------------------------- +16 | f(w = 3, x = 1, y = 2) // warn // warn // warn | ^^^^^ | the parameter name w is deprecated: use z instead +-- Deprecation Warning: tests/warn/i19077.scala:20:8 ------------------------------------------------------------------- +20 | X.f(v = 42) // warn + | ^^^^^^ + | the parameter name v is deprecated (since 3.3): use x instead diff --git a/tests/warn/i19077.scala b/tests/warn/i19077.scala index 91842f97a724..0c60cfb0d9ff 100644 --- a/tests/warn/i19077.scala +++ b/tests/warn/i19077.scala @@ -2,10 +2,25 @@ def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") z: Int) = x+y+z +object X: + def f(@deprecatedName("v", since="3.3") x: Int) = x + def f(@deprecatedName("v") x: Int, y: Int) = x+y + @main def Test = f(1, 2, 3) // nowarn f(1, 2, z = 3) // nowarn f(x = 1, 2, 3) // warn f(1, y = 2, 3) // warn f(1, 2, w = 3) // warn + f(x = 1, w = 3, y = 2) // warn // warn // warn f(w = 3, x = 1, y = 2) // warn // warn // warn + + X.f(42) + X.f(x = 42) + X.f(v = 42) // warn + X.f(x = 42, y = 27) + X.f(y = 42, x = 27) + +object empty: + def f(@deprecatedName("y", since="") x: Int) = x + def g = f(y = 42) // warn but omit empty since From 8435ae0b8e16207849f131731e5aab122304e84c Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 17 Feb 2025 13:45:40 -0800 Subject: [PATCH 4/5] Adjust info if paramss during overloading --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 1 + tests/neg/i19077.scala | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e7eda037117c..5bafdf91d350 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -350,6 +350,7 @@ object SymDenotations { ensureCompleted() if rawParamss.isEmpty then recurWithoutParamss(info) + else if symbol.hasAnnotation(defn.MappedAlternativeAnnot) then recurWithParamss(info.stripPoly, rawParamss) else recurWithParamss(info, rawParamss) end paramSymss diff --git a/tests/neg/i19077.scala b/tests/neg/i19077.scala index 26cddadbb83f..cbfb33e6dfe7 100644 --- a/tests/neg/i19077.scala +++ b/tests/neg/i19077.scala @@ -6,3 +6,8 @@ def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") f(1, 2, 3, w = 42) // error f(1, 2, w = 42, z = 27) // error f(1, 2, z = 42, w = 27) // error + +object X { def m[T](i: Int)(s: String) = s*i; def m[T](i: Int)(d: Double) = d*i } + +object Y: + def f = X.m(42)(s = "") // overloading resolution objects to methRef.symbol.paramSymss From 95c9088097cedadf87250b623996b6d309eea458 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 17 Feb 2025 14:56:27 -0800 Subject: [PATCH 5/5] Move MappedAlternative check into reorder --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 1 - compiler/src/dotty/tools/dotc/typer/Applications.scala | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 5bafdf91d350..e7eda037117c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -350,7 +350,6 @@ object SymDenotations { ensureCompleted() if rawParamss.isEmpty then recurWithoutParamss(info) - else if symbol.hasAnnotation(defn.MappedAlternativeAnnot) then recurWithParamss(info.stripPoly, rawParamss) else recurWithParamss(info, rawParamss) end paramSymss diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index fe4d3d6cfe31..27d7853e0b64 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -645,7 +645,11 @@ trait Applications extends Compatibility { version.map(v => s" (since $v)").getOrElse("") val deprecatedNames: Map[Name, Annotation] = - methRef.symbol.paramSymss.find(_.exists(_.isTerm)) match + val sym = methRef.symbol + val paramss = + if sym.hasAnnotation(defn.MappedAlternativeAnnot) then sym.rawParamss + else sym.paramSymss + paramss.find(_.exists(_.isTerm)) match case Some(ps) if ps.exists(_.hasAnnotation(defn.DeprecatedNameAnnot)) => ps.flatMap: p => p.getAnnotation(defn.DeprecatedNameAnnot).map(p.name -> _)