Skip to content

Commit 5c2217c

Browse files
committed
Named arg may be deprecatedName
1 parent ba32888 commit 5c2217c

File tree

8 files changed

+210
-56
lines changed

8 files changed

+210
-56
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ class Definitions {
10471047
@tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated")
10481048
@tu lazy val DeprecatedOverridingAnnot: ClassSymbol = requiredClass("scala.deprecatedOverriding")
10491049
@tu lazy val DeprecatedInheritanceAnnot: ClassSymbol = requiredClass("scala.deprecatedInheritance")
1050+
@tu lazy val DeprecatedNameAnnot: ClassSymbol = requiredClass("scala.deprecatedName")
10501051
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")
10511052
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
10521053
@tu lazy val InferredDepFunAnnot: ClassSymbol = requiredClass("scala.caps.internal.inferredDepFun")

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 98 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,8 @@ trait Applications extends Compatibility {
641641
def infoStr = if methType.isErroneous then "" else i": $methType"
642642
i"${err.refStr(methRef)}$infoStr"
643643

644+
private type TreeList[T <: Untyped] = List[Trees.Tree[T]]
645+
644646
/** Re-order arguments to correctly align named arguments
645647
* Issue errors in the following situations:
646648
*
@@ -652,67 +654,118 @@ trait Applications extends Compatibility {
652654
* (either named or positional), or
653655
* - The formal parameter at the argument position is also mentioned
654656
* in a subsequent named parameter.
655-
* - "parameter already instantiated" if a two named arguments have the same name.
657+
* - "parameter already instantiated" if two named arguments have the same name or deprecated alias.
656658
* - "does not have parameter" if a named parameter does not mention a formal
657659
* parameter name.
658660
*/
659-
def reorder[T <: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = {
661+
def reorder[T <: Untyped](args: TreeList[T]): TreeList[T] =
662+
663+
extension [A](list: List[A]) inline def dropOne = if list.isEmpty then list else list.tail // aka list.drop(1)
664+
665+
extension (dna: Annotation)
666+
def deprecatedName: Name =
667+
dna.argumentConstantString(0).map(_.toTermName).getOrElse(nme.NO_NAME)
668+
def since: String =
669+
val version = dna.argumentConstantString(1).filter(!_.isEmpty)
670+
version.map(v => s" (since $v)").getOrElse("")
660671

661-
/** @param pnames The list of parameter names that are missing arguments
672+
val deprecatedNames: Map[Name, Annotation] =
673+
val sym = methRef.symbol
674+
val paramss =
675+
if sym.hasAnnotation(defn.MappedAlternativeAnnot) then sym.rawParamss
676+
else sym.paramSymss
677+
paramss.find(_.exists(_.isTerm)) match
678+
case Some(ps) if ps.exists(_.hasAnnotation(defn.DeprecatedNameAnnot)) =>
679+
ps.flatMap: p =>
680+
p.getAnnotation(defn.DeprecatedNameAnnot).map(p.name -> _)
681+
.toMap
682+
case _ => Map.empty
683+
684+
extension (name: Name)
685+
def isMatchedBy(usage: Name): Boolean =
686+
name == usage
687+
|| deprecatedNames.get(name).exists(_.deprecatedName == usage)
688+
def checkDeprecationOf(usage: Name, pos: SrcPos): Unit = if !ctx.isAfterTyper then
689+
for dna <- deprecatedNames.get(name) do
690+
dna.deprecatedName match
691+
case `name` | nme.NO_NAME if name == usage =>
692+
report.deprecationWarning(em"naming parameter $usage is deprecated${dna.since}", pos)
693+
case `usage` =>
694+
report.deprecationWarning(em"the parameter name $usage is deprecated${dna.since}: use $name instead", pos)
695+
case _ =>
696+
def alternative: Name =
697+
deprecatedNames.get(name).map(_.deprecatedName).getOrElse(nme.NO_NAME)
698+
699+
/** Reorder the suffix of named args per a list of required names.
700+
*
701+
* @param pnames The list of parameter names that are missing arguments
662702
* @param args The list of arguments that are not yet passed, or that are waiting to be dropped
663703
* @param nameToArg A map from as yet unseen names to named arguments
664-
* @param toDrop A set of names that have already be passed as named arguments
704+
* @param toDrop A set of names that have already been passed as named arguments
705+
* @param missingArgs true if args were already missing, so error on positional
665706
*
666707
* For a well-typed application we have the invariants
667708
*
668709
* 1. `(args diff toDrop)` can be reordered to match `pnames`
669710
* 2. For every `(name -> arg)` in `nameToArg`, `arg` is an element of `args`
711+
*
712+
* Recurse over the parameter names looking for named args to use.
713+
* If there are no more parameters or no args fit, process the next arg:
714+
* a named arg may be previously used, or not yet used, or badly named.
670715
*/
671-
def handleNamed(pnames: List[Name], args: List[Trees.Tree[T]],
716+
def handleNamed(pnames: List[Name], args: TreeList[T],
672717
nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name],
673-
missingArgs: Boolean): List[Trees.Tree[T]] = pnames match {
674-
case pname :: pnames1 if nameToArg contains pname =>
675-
// there is a named argument for this parameter; pick it
676-
nameToArg(pname) :: handleNamed(pnames1, args, nameToArg - pname, toDrop + pname, missingArgs)
718+
missingArgs: Boolean): TreeList[T] =
719+
pnames match
720+
case pname :: pnames if nameToArg.contains(pname) =>
721+
val arg = nameToArg(pname) // use the named argument for this parameter
722+
pname.checkDeprecationOf(pname, arg.srcPos)
723+
arg :: handleNamed(pnames, args, nameToArg - pname, toDrop + pname, missingArgs)
724+
case pname :: pnames if nameToArg.contains(pname.alternative) =>
725+
val alt = pname.alternative
726+
val arg = nameToArg(alt) // use the named argument for this parameter
727+
pname.checkDeprecationOf(alt, arg.srcPos)
728+
arg :: handleNamed(pnames, args, nameToArg - alt, toDrop + alt, missingArgs)
677729
case _ =>
678-
def pnamesRest = if (pnames.isEmpty) pnames else pnames.tail
679-
args match {
680-
case (arg @ NamedArg(aname, _)) :: args1 =>
681-
if (toDrop contains aname) // argument is already passed
682-
handleNamed(pnames, args1, nameToArg, toDrop - aname, missingArgs)
683-
else if ((nameToArg contains aname) && pnames.nonEmpty) // argument is missing, pass an empty tree
684-
genericEmptyTree :: handleNamed(pnames.tail, args, nameToArg, toDrop, missingArgs = true)
685-
else { // name not (or no longer) available for named arg
686-
def msg =
687-
if (methodType.paramNames contains aname)
688-
em"parameter $aname of $methString is already instantiated"
689-
else
690-
em"$methString does not have a parameter $aname"
691-
fail(msg, arg.asInstanceOf[Arg])
692-
arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop, missingArgs)
693-
}
694-
case arg :: args1 =>
695-
if toDrop.nonEmpty || missingArgs then
696-
report.error(i"positional after named argument", arg.srcPos)
697-
arg :: handleNamed(pnamesRest, args1, nameToArg, toDrop, missingArgs) // unnamed argument; pick it
698-
case Nil => // no more args, continue to pick up any preceding named args
699-
if (pnames.isEmpty) Nil
700-
else handleNamed(pnamesRest, args, nameToArg, toDrop, missingArgs)
701-
}
702-
}
703-
704-
def handlePositional(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] =
705-
args match {
706-
case (arg: NamedArg @unchecked) :: _ =>
707-
val nameAssocs = for (case arg @ NamedArg(name, _) <- args) yield (name, arg)
708-
handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set(), missingArgs = false)
709-
case arg :: args1 =>
710-
arg :: handlePositional(if (pnames.isEmpty) Nil else pnames.tail, args1)
711-
case Nil => Nil
712-
}
730+
args match
731+
case allArgs @ (arg @ NamedArg(aname, _)) :: args =>
732+
if toDrop.contains(aname) then
733+
// named argument was already picked (using aname), skip it
734+
handleNamed(pnames, args, nameToArg, toDrop - aname, missingArgs)
735+
else if pnames.nonEmpty && nameToArg.contains(aname) then
736+
// argument for pname is missing, pass an empty tree; arg may be used later, so keep it
737+
genericEmptyTree :: handleNamed(pnames.tail, allArgs, nameToArg, toDrop, missingArgs = true)
738+
else // name not (or no longer) available for named arg
739+
def msg =
740+
if methodType.paramNames.exists(nm => nm == aname || nm.alternative == aname) then
741+
em"parameter $aname of $methString is already instantiated"
742+
else
743+
em"$methString does not have a parameter $aname"
744+
fail(msg, arg.asInstanceOf[Arg])
745+
arg :: handleNamed(pnames.dropOne, args, nameToArg, toDrop, missingArgs)
746+
case arg :: args =>
747+
if toDrop.nonEmpty || missingArgs then
748+
report.error(i"positional after named argument", arg.srcPos)
749+
arg :: handleNamed(pnames.dropOne, args, nameToArg, toDrop, missingArgs) // unnamed argument; pick it
750+
case nil => // no more args, continue to pick up any preceding named args
751+
if pnames.isEmpty then nil
752+
else handleNamed(pnames.dropOne, args = nil, nameToArg, toDrop, missingArgs)
753+
754+
// Skip prefix of positional args, then handleNamed
755+
def handlePositional(pnames: List[Name], args: TreeList[T]): TreeList[T] =
756+
args match
757+
case (arg @ NamedArg(name, _)) :: args if !pnames.isEmpty && pnames.head.isMatchedBy(name) =>
758+
pnames.head.checkDeprecationOf(name, arg.srcPos)
759+
arg :: handlePositional(pnames.tail, args)
760+
case (_: NamedArg) :: _ =>
761+
val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg }
762+
handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false)
763+
case arg :: args =>
764+
arg :: handlePositional(pnames.dropOne, args)
765+
case nil => nil
713766

714767
handlePositional(methodType.paramNames, args)
715-
}
768+
end reorder
716769

717770
/** Is `sym` a constructor of a Java-defined annotation? */
718771
def isJavaAnnotConstr(sym: Symbol): Boolean =

tests/neg/i18122.check

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@
3030
23 | bar1(ys = 1) // error: missing arg
3131
| ^^^^^^^^^^^^
3232
| missing argument for parameter x of method bar1 in object Test: (x: Int, ys: Int*): Unit
33-
-- Error: tests/neg/i18122.scala:43:16 ---------------------------------------------------------------------------------
34-
43 | bar1(x = 1, 2, ys = 3) // error: positional after named
35-
| ^
36-
| positional after named argument
33+
-- Error: tests/neg/i18122.scala:43:22 ---------------------------------------------------------------------------------
34+
43 | bar1(x = 1, 2, ys = 3) // error: parameter ys is already instantiated
35+
| ^^^^^^
36+
| parameter ys of method bar1 in object Test: (x: Int, ys: Int*): Unit is already instantiated
3737
-- Error: tests/neg/i18122.scala:44:18 ---------------------------------------------------------------------------------
3838
44 | bar1(1, 2, ys = 3) // error: parameter ys is already instantiated
3939
| ^^^^^^
4040
| parameter ys of method bar1 in object Test: (x: Int, ys: Int*): Unit is already instantiated
41-
-- Error: tests/neg/i18122.scala:45:16 ---------------------------------------------------------------------------------
42-
45 | bar2(x = 1, 2, ys = 3) // error: positional after named
43-
| ^
44-
| positional after named argument
41+
-- Error: tests/neg/i18122.scala:45:22 ---------------------------------------------------------------------------------
42+
45 | bar2(x = 1, 2, ys = 3) // error: parameter ys is already instantiated
43+
| ^^^^^^
44+
| parameter ys of method bar2 in object Test: (x: Int, ys: Int*): Unit is already instantiated
4545
-- Error: tests/neg/i18122.scala:46:17 ---------------------------------------------------------------------------------
4646
46 | bar1(ys = 1, 2, x = 3) // error: positional after named
4747
| ^

tests/neg/i18122.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ object Test {
4040
bar2(x = 1, 2, 3)
4141
bar1(x = 1, ys = 2, 3)
4242
bar2(x = 1, ys = 2, 3)
43-
bar1(x = 1, 2, ys = 3) // error: positional after named
43+
bar1(x = 1, 2, ys = 3) // error: parameter ys is already instantiated
4444
bar1(1, 2, ys = 3) // error: parameter ys is already instantiated
45-
bar2(x = 1, 2, ys = 3) // error: positional after named
45+
bar2(x = 1, 2, ys = 3) // error: parameter ys is already instantiated
4646
bar1(ys = 1, 2, x = 3) // error: positional after named
4747
bar2(ys = 1, 2, x = 3) // error: positional after named
4848
}
49-
}
49+
}

tests/neg/i19077.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- Error: tests/neg/i19077.scala:5:15 ----------------------------------------------------------------------------------
2+
5 | f(1, 2, 3, x = 42) // error
3+
| ^^^^^^
4+
| parameter x of method f: (x: Int, y: Int, z: Int): Int is already instantiated
5+
-- Error: tests/neg/i19077.scala:6:15 ----------------------------------------------------------------------------------
6+
6 | f(1, 2, 3, w = 42) // error
7+
| ^^^^^^
8+
| parameter w of method f: (x: Int, y: Int, z: Int): Int is already instantiated
9+
-- Error: tests/neg/i19077.scala:7:20 ----------------------------------------------------------------------------------
10+
7 | f(1, 2, w = 42, z = 27) // error
11+
| ^^^^^^
12+
| parameter z of method f: (x: Int, y: Int, z: Int): Int is already instantiated
13+
-- Error: tests/neg/i19077.scala:8:20 ----------------------------------------------------------------------------------
14+
8 | f(1, 2, z = 42, w = 27) // error
15+
| ^^^^^^
16+
| parameter w of method f: (x: Int, y: Int, z: Int): Int is already instantiated
17+
there was 1 deprecation warning; re-run with -deprecation for details

tests/neg/i19077.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") z: Int) = x+y+z
3+
4+
@main def Test =
5+
f(1, 2, 3, x = 42) // error
6+
f(1, 2, 3, w = 42) // error
7+
f(1, 2, w = 42, z = 27) // error
8+
f(1, 2, z = 42, w = 27) // error
9+
10+
object X { def m[T](i: Int)(s: String) = s*i; def m[T](i: Int)(d: Double) = d*i }
11+
12+
object Y:
13+
def f = X.m(42)(s = "") // overloading resolution objects to methRef.symbol.paramSymss

tests/warn/i19077.check

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
-- Deprecation Warning: tests/warn/i19077.scala:26:14 ------------------------------------------------------------------
2+
26 | def g = f(y = 42) // warn but omit empty since
3+
| ^^^^^^
4+
| the parameter name y is deprecated: use x instead
5+
-- Deprecation Warning: tests/warn/i19077.scala:12:6 -------------------------------------------------------------------
6+
12 | f(x = 1, 2, 3) // warn
7+
| ^^^^^
8+
| naming parameter x is deprecated
9+
-- Deprecation Warning: tests/warn/i19077.scala:13:9 -------------------------------------------------------------------
10+
13 | f(1, y = 2, 3) // warn
11+
| ^^^^^
12+
| naming parameter y is deprecated
13+
-- Deprecation Warning: tests/warn/i19077.scala:14:12 ------------------------------------------------------------------
14+
14 | f(1, 2, w = 3) // warn
15+
| ^^^^^
16+
| the parameter name w is deprecated: use z instead
17+
-- Deprecation Warning: tests/warn/i19077.scala:15:6 -------------------------------------------------------------------
18+
15 | f(x = 1, w = 3, y = 2) // warn // warn // warn
19+
| ^^^^^
20+
| naming parameter x is deprecated
21+
-- Deprecation Warning: tests/warn/i19077.scala:15:20 ------------------------------------------------------------------
22+
15 | f(x = 1, w = 3, y = 2) // warn // warn // warn
23+
| ^^^^^
24+
| naming parameter y is deprecated
25+
-- Deprecation Warning: tests/warn/i19077.scala:15:13 ------------------------------------------------------------------
26+
15 | f(x = 1, w = 3, y = 2) // warn // warn // warn
27+
| ^^^^^
28+
| the parameter name w is deprecated: use z instead
29+
-- Deprecation Warning: tests/warn/i19077.scala:16:13 ------------------------------------------------------------------
30+
16 | f(w = 3, x = 1, y = 2) // warn // warn // warn
31+
| ^^^^^
32+
| naming parameter x is deprecated
33+
-- Deprecation Warning: tests/warn/i19077.scala:16:20 ------------------------------------------------------------------
34+
16 | f(w = 3, x = 1, y = 2) // warn // warn // warn
35+
| ^^^^^
36+
| naming parameter y is deprecated
37+
-- Deprecation Warning: tests/warn/i19077.scala:16:6 -------------------------------------------------------------------
38+
16 | f(w = 3, x = 1, y = 2) // warn // warn // warn
39+
| ^^^^^
40+
| the parameter name w is deprecated: use z instead
41+
-- Deprecation Warning: tests/warn/i19077.scala:20:8 -------------------------------------------------------------------
42+
20 | X.f(v = 42) // warn
43+
| ^^^^^^
44+
| the parameter name v is deprecated (since 3.3): use x instead

tests/warn/i19077.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//> using options -deprecation
2+
3+
def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") z: Int) = x+y+z
4+
5+
object X:
6+
def f(@deprecatedName("v", since="3.3") x: Int) = x
7+
def f(@deprecatedName("v") x: Int, y: Int) = x+y
8+
9+
@main def Test =
10+
f(1, 2, 3) // nowarn
11+
f(1, 2, z = 3) // nowarn
12+
f(x = 1, 2, 3) // warn
13+
f(1, y = 2, 3) // warn
14+
f(1, 2, w = 3) // warn
15+
f(x = 1, w = 3, y = 2) // warn // warn // warn
16+
f(w = 3, x = 1, y = 2) // warn // warn // warn
17+
18+
X.f(42)
19+
X.f(x = 42)
20+
X.f(v = 42) // warn
21+
X.f(x = 42, y = 27)
22+
X.f(y = 42, x = 27)
23+
24+
object empty:
25+
def f(@deprecatedName("y", since="") x: Int) = x
26+
def g = f(y = 42) // warn but omit empty since

0 commit comments

Comments
 (0)