Skip to content

Commit f085145

Browse files
committed
Implement automatic rewrites for named infix arguments interpreted as named tuples
1 parent ad3f38d commit f085145

File tree

13 files changed

+106
-32
lines changed

13 files changed

+106
-32
lines changed

compiler/src/dotty/tools/dotc/config/MigrationVersion.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion)
2626
case WithOperator extends MigrationVersion(`3.4`, future)
2727
case FunctionUnderscore extends MigrationVersion(`3.4`, future)
2828
case NonNamedArgumentInJavaAnnotation extends MigrationVersion(`3.6`, `3.6`)
29+
case AmbiguousNamedTupleInfixApply extends MigrationVersion(`3.6`, never)
2930
case ImportWildcard extends MigrationVersion(future, future)
3031
case ImportRename extends MigrationVersion(future, future)
3132
case ParameterEnclosedByParenthesis extends MigrationVersion(future, future)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ object StdNames {
668668
val readResolve: N = "readResolve"
669669
val zero: N = "zero"
670670
val zip: N = "zip"
671+
val `++` : N = "++"
671672
val nothingRuntimeClass: N = "scala.runtime.Nothing$"
672673
val nullRuntimeClass: N = "scala.runtime.Null$"
673674

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,21 +1120,32 @@ object Parsers {
11201120
if (prec < opPrec || leftAssoc && prec == opPrec) {
11211121
opStack = opStack.tail
11221122
recur {
1123-
atSpan(opInfo.operator.span union opInfo.operand.span union top.span):
1124-
def deprecateInfixNamedArg(t: Tree): Unit = t match
1125-
case Tuple(ts) => ts.foreach(deprecateInfixNamedArg)
1126-
case Parens(t) => deprecateInfixNamedArg(t)
1127-
case t: NamedArg => report.deprecationWarning(InfixNamedArgDeprecation(), t.srcPos)
1128-
case _ =>
1129-
deprecateInfixNamedArg(top)
1130-
InfixOp(opInfo.operand, opInfo.operator, top)
1123+
migrateInfixOp(opInfo, isType):
1124+
atSpan(opInfo.operator.span union opInfo.operand.span union top.span):
1125+
InfixOp(opInfo.operand, opInfo.operator, top)
11311126
}
11321127
}
11331128
else top
11341129
}
11351130
recur(top)
11361131
}
11371132

1133+
private def migrateInfixOp(opInfo: OpInfo, isType: Boolean)(infixOp: InfixOp): Tree = {
1134+
def isNamedTupleOperator = opInfo.operator.name match
1135+
case nme.EQ | nme.NE | nme.eq | nme.ne | nme.`++` | nme.zip => true
1136+
case _ => false
1137+
if isType then infixOp
1138+
else infixOp.right match
1139+
case Tuple(args) if args.exists(_.isInstanceOf[NamedArg]) && !isNamedTupleOperator =>
1140+
report.errorOrMigrationWarning(AmbiguousNamedTupleInfixApply(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleInfixApply)
1141+
if MigrationVersion.AmbiguousNamedTupleInfixApply.needsPatch then
1142+
val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), args)
1143+
patch(source, infixOp.span, asApply.show)
1144+
asApply // allow to use pre-3.6 syntax in migration mode
1145+
else infixOp
1146+
case _ => infixOp
1147+
}
1148+
11381149
/** True if we are seeing a lambda argument after a colon of the form:
11391150
* : (params) =>
11401151
* body

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
217217
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
218218
case QuotedTypeMissingID // errorNumber: 202
219219
case AmbiguousNamedTupleAssignmentID // errorNumber: 203
220-
case DeprecatedNamedInfixArgID // errorNumber: 204
220+
case AmbiguousNamedTupleInfixApplyID // errorNumber: 204
221221

222222
def errorNumber = ordinal - 1
223223

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3353,9 +3353,12 @@ final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Co
33533353

33543354
override protected def explain(using Context): String = ""
33553355

3356-
class InfixNamedArgDeprecation()(using Context) extends SyntaxMsg(DeprecatedNamedInfixArgID):
3357-
def msg(using Context) = "Named argument syntax is deprecated for infix application"
3356+
class AmbiguousNamedTupleInfixApply()(using Context) extends SyntaxMsg(AmbiguousNamedTupleInfixApplyID):
3357+
def msg(using Context) =
3358+
"Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list."
3359+
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)
3360+
33583361
def explain(using Context) =
3359-
i"""The argument will be parsed as a named tuple in future.
3362+
i"""Starting with Scala 3.6 infix named arguments are interpretted as Named Tuple.
33603363
|
33613364
|To avoid this warning, either remove the argument names or use dotted selection."""

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class CompilationTests {
7979
compileFile("tests/rewrites/i20002.scala", defaultOptions.and("-indent", "-rewrite")),
8080
compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")),
8181
compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")),
82+
compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
8283
).checkRewrites()
8384
}
8485

tests/neg/infix-named-args.check

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------
2-
2 | def f = 42 + (x = 1) // error // a named tuple!
2+
2 | def f = 42 + (x = 1) // error // werror
33
| ^^^^
44
| None of the overloaded alternatives of method + in class Int with types
55
| (x: Double): Double
@@ -11,11 +11,31 @@
1111
| (x: Byte): Int
1212
| (x: String): String
1313
| match arguments ((x : Int)) (a named tuple)
14-
-- [E007] Type Mismatch Error: tests/neg/infix-named-args.scala:13:18 --------------------------------------------------
15-
13 | def g = this ** 2 // error
16-
| ^
17-
| Found: (2 : Int)
18-
| Required: X
14+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 --------------------------------------------------------
15+
2 | def f = 42 + (x = 1) // error // werror
16+
| ^^^^^^^
17+
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
18+
|This can be rewritten automatically under -rewrite -source 3.6-migration.
19+
|
20+
| longer explanation available when compiling with `-explain`
21+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:5:26 --------------------------------------------------------
22+
5 | def g = new C() `multi` (x = 42, y = 27) // werror
23+
| ^^^^^^^^^^^^^^^^
24+
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
25+
|This can be rewritten automatically under -rewrite -source 3.6-migration.
26+
|
27+
| longer explanation available when compiling with `-explain`
28+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:6:21 --------------------------------------------------------
29+
6 | def h = new C() ** (x = 42, y = 27) // werror
30+
| ^^^^^^^^^^^^^^^^
31+
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
32+
|This can be rewritten automatically under -rewrite -source 3.6-migration.
33+
|
34+
| longer explanation available when compiling with `-explain`
35+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:13:18 -------------------------------------------------------
36+
13 | def f = this ** (x = 2) // werror
37+
| ^^^^^^^
38+
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
39+
|This can be rewritten automatically under -rewrite -source 3.6-migration.
1940
|
2041
| longer explanation available when compiling with `-explain`
21-
there were 6 deprecation warnings; re-run with -deprecation for details

tests/neg/infix-named-args.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
class C:
2-
def f = 42 + (x = 1) // error // a named tuple!
2+
def f = 42 + (x = 1) // error // werror
33
def multi(x: Int, y: Int): Int = x + y
44
def **(x: Int, y: Int): Int = x + y
5-
def g = new C() `multi` (x = 42, y = 27) // werror // werror // not actually a tuple! appearances to the contrary
6-
def h = new C() ** (x = 42, y = 27) // werror // werror
5+
def g = new C() `multi` (x = 42, y = 27) // werror
6+
def h = new C() ** (x = 42, y = 27) // werror
77

88
type X = (x: Int)
99

1010
class D(d: Int):
11+
def **(x: Int): Int = d * x
1112
def **(x: X): Int = d * x.x
12-
def f = this ** (x = 2)
13-
def g = this ** 2 // error
13+
def f = this ** (x = 2) // werror
14+
def g = this ** 2

tests/neg/named-tuples.check

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,3 @@
101101
| Required: (name : ?, age : ?)
102102
|
103103
| longer explanation available when compiling with `-explain`
104-
there were 2 deprecation warnings; re-run with -deprecation for details
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class C:
2+
def multi(x: Int, y: Int): Int = x + y
3+
def **(x: Int, y: Int): Int = x + y
4+
def g = new C().multi(x = 42, y = 27)
5+
def h = new C().**(x = 42, y = 27)
6+
7+
type X = (x: Int)
8+
9+
class D(d: Int):
10+
def **(x: Int): Int = d * x
11+
def **(x: X): Int = d * x.x
12+
def f = this.**(x = 2)
13+
def g = this ** 2
14+
def h = this ** ((x = 2))
15+
def i = this.**(x = (1 + 1))

0 commit comments

Comments
 (0)