Skip to content

Commit 225eaec

Browse files
committed
Clean up and correct
1 parent da42220 commit 225eaec

File tree

5 files changed

+160
-77
lines changed

5 files changed

+160
-77
lines changed

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

Lines changed: 84 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ trait Applications extends Compatibility {
616616
def infoStr = if methType.isErroneous then "" else i": $methType"
617617
i"${err.refStr(methRef)}$infoStr"
618618

619+
private type TreeList[T <: Untyped] = List[Trees.Tree[T]]
620+
619621
/** Re-order arguments to correctly align named arguments
620622
* Issue errors in the following situations:
621623
*
@@ -627,28 +629,43 @@ trait Applications extends Compatibility {
627629
* (either named or positional), or
628630
* - The formal parameter at the argument position is also mentioned
629631
* in a subsequent named parameter.
630-
* - "parameter already instantiated" if a two named arguments have the same name.
632+
* - "parameter already instantiated" if two named arguments have the same name or deprecated alias.
631633
* - "does not have parameter" if a named parameter does not mention a formal
632634
* parameter name.
633635
*/
634-
def reorder[T <: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] =
635-
636-
inline def tailOf[A](list: List[A]): List[A] = if list.isEmpty then list else list.tail // list.drop(1)
637-
638-
def hasDeprecatedName(pname: Name, other: Name, t: Trees.Tree[T]): Boolean = !ctx.isAfterTyper &&
639-
methRef.symbol.paramSymss.flatten.find(_.name == pname).flatMap(_.getAnnotation(defn.DeprecatedNameAnnot)).match
640-
case Some(annot) =>
641-
val name = annot.argumentConstantString(0)
642-
val version = annot.argumentConstantString(1).filter(!_.isEmpty)
643-
val since = version.map(v => s" (since $v)").getOrElse("")
644-
name.map(_.toTermName) match
645-
case Some(`other`) =>
646-
report.deprecationWarning(em"the parameter name $other is deprecated$since: use $pname instead", t.srcPos)
647-
case Some(`pname`) | None =>
648-
report.deprecationWarning(em"naming parameter $pname is deprecated$since", t.srcPos)
649-
case _ =>
650-
true
651-
case _ => false
636+
def reorder[T <: Untyped](args: TreeList[T]): TreeList[T] =
637+
638+
extension [A](list: List[A]) inline def dropOne = if list.isEmpty then list else list.tail // aka list.drop(1)
639+
640+
extension (dna: Annotation)
641+
def deprecatedName: Name =
642+
dna.argumentConstantString(0).map(_.toTermName).getOrElse(nme.NO_NAME)
643+
def since: String =
644+
val version = dna.argumentConstantString(1).filter(!_.isEmpty)
645+
version.map(v => s" (since $v)").getOrElse("")
646+
647+
val deprecatedNames: Map[Name, Annotation] =
648+
methRef.symbol.paramSymss.find(_.exists(_.isTerm)) match
649+
case Some(ps) if ps.exists(_.hasAnnotation(defn.DeprecatedNameAnnot)) =>
650+
ps.flatMap: p =>
651+
p.getAnnotation(defn.DeprecatedNameAnnot).map(p.name -> _)
652+
.toMap
653+
case _ => Map.empty
654+
655+
extension (name: Name)
656+
def isMatchedBy(usage: Name): Boolean =
657+
name == usage
658+
|| deprecatedNames.get(name).exists(_.deprecatedName == usage)
659+
def checkDeprecationOf(usage: Name, pos: SrcPos): Unit = if !ctx.isAfterTyper then
660+
for dna <- deprecatedNames.get(name) do
661+
dna.deprecatedName match
662+
case `name` | nme.NO_NAME if name == usage =>
663+
report.deprecationWarning(em"naming parameter $usage is deprecated${dna.since}", pos)
664+
case `usage` =>
665+
report.deprecationWarning(em"the parameter name $usage is deprecated${dna.since}: use $name instead", pos)
666+
case _ =>
667+
def alternative: Name =
668+
deprecatedNames.get(name).map(_.deprecatedName).getOrElse(nme.NO_NAME)
652669

653670
/** Reorder the suffix of named args per a list of required names.
654671
*
@@ -662,55 +679,61 @@ trait Applications extends Compatibility {
662679
*
663680
* 1. `(args diff toDrop)` can be reordered to match `pnames`
664681
* 2. For every `(name -> arg)` in `nameToArg`, `arg` is an element of `args`
682+
*
683+
* Recurse over the parameter names looking for named args to use.
684+
* If there are no more parameters or no args fit, process the next arg:
685+
* a named arg may be previously used, or not yet used, or badly named.
665686
*/
666-
def handleNamed(pnames: List[Name], args: List[Trees.Tree[T]],
687+
def handleNamed(pnames: List[Name], args: TreeList[T],
667688
nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name],
668-
missingArgs: Boolean): List[Trees.Tree[T]] =
689+
missingArgs: Boolean): TreeList[T] =
669690
pnames match
670-
case pname :: pnames if nameToArg.contains(pname) => // use the named argument for this parameter
671-
val arg = nameToArg(pname)
672-
hasDeprecatedName(pname, nme.NO_NAME, arg)
691+
case pname :: pnames if nameToArg.contains(pname) =>
692+
val arg = nameToArg(pname) // use the named argument for this parameter
693+
pname.checkDeprecationOf(pname, arg.srcPos)
673694
arg :: handleNamed(pnames, args, nameToArg - pname, toDrop + pname, missingArgs)
695+
case pname :: pnames if nameToArg.contains(pname.alternative) =>
696+
val alt = pname.alternative
697+
val arg = nameToArg(alt) // use the named argument for this parameter
698+
pname.checkDeprecationOf(alt, arg.srcPos)
699+
arg :: handleNamed(pnames, args, nameToArg - alt, toDrop + alt, missingArgs)
674700
case _ =>
675701
args match
676-
case allArgs @ (arg @ NamedArg(aname, _)) :: args =>
677-
if toDrop.contains(aname) then // named argument was already picked
678-
handleNamed(pnames, args, nameToArg, toDrop - aname, missingArgs)
679-
else if pnames.nonEmpty && nameToArg.contains(aname) then
680-
val pname = pnames.head
681-
if hasDeprecatedName(pname, aname, arg) then // name was deprecated alt, so try again with canonical name
682-
val parg = cpy.NamedArg(arg)(pname, arg.arg).asInstanceOf[Trees.NamedArg[T]]
683-
handleNamed(pnames, parg :: args, nameToArg.removed(aname).updated(pname, parg), toDrop, missingArgs)
684-
else // argument for pname is missing, pass an empty tree
685-
genericEmptyTree :: handleNamed(pnames.tail, allArgs, nameToArg, toDrop, missingArgs = true)
686-
else // name not (or no longer) available for named arg
687-
def msg =
688-
if methodType.paramNames.contains(aname) then
689-
em"parameter $aname of $methString is already instantiated"
690-
else
691-
em"$methString does not have a parameter $aname"
692-
fail(msg, arg.asInstanceOf[Arg])
693-
arg :: handleNamed(tailOf(pnames), args, nameToArg, toDrop, missingArgs)
694-
case arg :: args =>
695-
if toDrop.nonEmpty || missingArgs then
696-
report.error(i"positional after named argument", arg.srcPos)
697-
arg :: handleNamed(tailOf(pnames), args, 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 then nil
700-
else handleNamed(tailOf(pnames), nil, nameToArg, toDrop, missingArgs)
701-
702-
/** Skip prefix of positional args, then handleNamed */
703-
def handlePositional(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] =
704-
args match
705-
case (arg @ NamedArg(name, _)) :: args if !pnames.isEmpty && pnames.head == name =>
706-
hasDeprecatedName(name, nme.NO_NAME, arg)
707-
arg :: handlePositional(pnames.tail, args)
708-
case (_: NamedArg) :: _ =>
709-
val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg }
710-
handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false)
702+
case allArgs @ (arg @ NamedArg(aname, _)) :: args =>
703+
if toDrop.contains(aname) then
704+
// named argument was already picked (using aname), skip it
705+
handleNamed(pnames, args, nameToArg, toDrop - aname, missingArgs)
706+
else if pnames.nonEmpty && nameToArg.contains(aname) then
707+
// argument for pname is missing, pass an empty tree; arg may be used later, so keep it
708+
genericEmptyTree :: handleNamed(pnames.tail, allArgs, nameToArg, toDrop, missingArgs = true)
709+
else // name not (or no longer) available for named arg
710+
def msg =
711+
if methodType.paramNames.exists(nm => nm == aname || nm.alternative == aname) then
712+
em"parameter $aname of $methString is already instantiated"
713+
else
714+
em"$methString does not have a parameter $aname"
715+
fail(msg, arg.asInstanceOf[Arg])
716+
arg :: handleNamed(pnames.dropOne, args, nameToArg, toDrop, missingArgs)
711717
case arg :: args =>
712-
arg :: handlePositional(tailOf(pnames), args)
713-
case nil => nil
718+
if toDrop.nonEmpty || missingArgs then
719+
report.error(i"positional after named argument", arg.srcPos)
720+
arg :: handleNamed(pnames.dropOne, args, nameToArg, toDrop, missingArgs) // unnamed argument; pick it
721+
case nil => // no more args, continue to pick up any preceding named args
722+
if pnames.isEmpty then nil
723+
else handleNamed(pnames.dropOne, args = nil, nameToArg, toDrop, missingArgs)
724+
725+
// Skip prefix of positional args, then handleNamed
726+
def handlePositional(pnames: List[Name], args: TreeList[T]): TreeList[T] =
727+
args match
728+
case (arg @ NamedArg(name, _)) :: args if !pnames.isEmpty && pnames.head.isMatchedBy(name) =>
729+
pnames.head.checkDeprecationOf(name, arg.srcPos)
730+
arg :: handlePositional(pnames.tail, args)
731+
case (_: NamedArg) :: _ =>
732+
val nameAssocs = args.collect { case arg @ NamedArg(name, _) => name -> arg }
733+
handleNamed(pnames, args, nameAssocs.toMap, toDrop = Set.empty, missingArgs = false)
734+
case arg :: args =>
735+
arg :: handlePositional(pnames.dropOne, args)
736+
case nil => nil
714737

715738
handlePositional(methodType.paramNames, args)
716739
end reorder

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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

tests/warn/i19077.check

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
1-
-- Deprecation Warning: tests/warn/i19077.scala:8:6 --------------------------------------------------------------------
2-
8 | f(x = 1, 2, 3) // warn
3-
| ^^^^^
4-
| naming parameter x is deprecated
5-
-- Deprecation Warning: tests/warn/i19077.scala:9:9 --------------------------------------------------------------------
6-
9 | f(1, y = 2, 3) // warn
7-
| ^^^^^
8-
| naming parameter y is deprecated
9-
-- Deprecation Warning: tests/warn/i19077.scala:10:12 ------------------------------------------------------------------
10-
10 | f(1, 2, w = 3) // warn
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
1115
| ^^^^^
1216
| the parameter name w is deprecated: use z instead
13-
-- Deprecation Warning: tests/warn/i19077.scala:11:13 ------------------------------------------------------------------
14-
11 | f(w = 3, x = 1, y = 2) // warn // warn // warn
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
1531
| ^^^^^
1632
| naming parameter x is deprecated
17-
-- Deprecation Warning: tests/warn/i19077.scala:11:20 ------------------------------------------------------------------
18-
11 | f(w = 3, x = 1, y = 2) // warn // warn // warn
33+
-- Deprecation Warning: tests/warn/i19077.scala:16:20 ------------------------------------------------------------------
34+
16 | f(w = 3, x = 1, y = 2) // warn // warn // warn
1935
| ^^^^^
2036
| naming parameter y is deprecated
21-
-- Deprecation Warning: tests/warn/i19077.scala:11:6 -------------------------------------------------------------------
22-
11 | f(w = 3, x = 1, y = 2) // warn // warn // warn
37+
-- Deprecation Warning: tests/warn/i19077.scala:16:6 -------------------------------------------------------------------
38+
16 | f(w = 3, x = 1, y = 2) // warn // warn // warn
2339
| ^^^^^
2440
| 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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,25 @@
22

33
def f(@deprecatedName("x") x: Int, @deprecatedName y: Int, @deprecatedName("w") z: Int) = x+y+z
44

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+
59
@main def Test =
610
f(1, 2, 3) // nowarn
711
f(1, 2, z = 3) // nowarn
812
f(x = 1, 2, 3) // warn
913
f(1, y = 2, 3) // warn
1014
f(1, 2, w = 3) // warn
15+
f(x = 1, w = 3, y = 2) // warn // warn // warn
1116
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)