Skip to content

Commit

Permalink
Implement automatic rewrites for named infix arguments interpreted as…
Browse files Browse the repository at this point in the history
… named tuples
  • Loading branch information
WojciechMazur committed Nov 14, 2024
1 parent ad3f38d commit f085145
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion)
case WithOperator extends MigrationVersion(`3.4`, future)
case FunctionUnderscore extends MigrationVersion(`3.4`, future)
case NonNamedArgumentInJavaAnnotation extends MigrationVersion(`3.6`, `3.6`)
case AmbiguousNamedTupleInfixApply extends MigrationVersion(`3.6`, never)
case ImportWildcard extends MigrationVersion(future, future)
case ImportRename extends MigrationVersion(future, future)
case ParameterEnclosedByParenthesis extends MigrationVersion(future, future)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ object StdNames {
val readResolve: N = "readResolve"
val zero: N = "zero"
val zip: N = "zip"
val `++` : N = "++"
val nothingRuntimeClass: N = "scala.runtime.Nothing$"
val nullRuntimeClass: N = "scala.runtime.Null$"

Expand Down
27 changes: 19 additions & 8 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1120,21 +1120,32 @@ object Parsers {
if (prec < opPrec || leftAssoc && prec == opPrec) {
opStack = opStack.tail
recur {
atSpan(opInfo.operator.span union opInfo.operand.span union top.span):
def deprecateInfixNamedArg(t: Tree): Unit = t match
case Tuple(ts) => ts.foreach(deprecateInfixNamedArg)
case Parens(t) => deprecateInfixNamedArg(t)
case t: NamedArg => report.deprecationWarning(InfixNamedArgDeprecation(), t.srcPos)
case _ =>
deprecateInfixNamedArg(top)
InfixOp(opInfo.operand, opInfo.operator, top)
migrateInfixOp(opInfo, isType):
atSpan(opInfo.operator.span union opInfo.operand.span union top.span):
InfixOp(opInfo.operand, opInfo.operator, top)
}
}
else top
}
recur(top)
}

private def migrateInfixOp(opInfo: OpInfo, isType: Boolean)(infixOp: InfixOp): Tree = {
def isNamedTupleOperator = opInfo.operator.name match
case nme.EQ | nme.NE | nme.eq | nme.ne | nme.`++` | nme.zip => true
case _ => false
if isType then infixOp
else infixOp.right match
case Tuple(args) if args.exists(_.isInstanceOf[NamedArg]) && !isNamedTupleOperator =>
report.errorOrMigrationWarning(AmbiguousNamedTupleInfixApply(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleInfixApply)
if MigrationVersion.AmbiguousNamedTupleInfixApply.needsPatch then
val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), args)
patch(source, infixOp.span, asApply.show)
asApply // allow to use pre-3.6 syntax in migration mode
else infixOp
case _ => infixOp
}

/** True if we are seeing a lambda argument after a colon of the form:
* : (params) =>
* body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
case QuotedTypeMissingID // errorNumber: 202
case AmbiguousNamedTupleAssignmentID // errorNumber: 203
case DeprecatedNamedInfixArgID // errorNumber: 204
case AmbiguousNamedTupleInfixApplyID // errorNumber: 204

def errorNumber = ordinal - 1

Expand Down
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3353,9 +3353,12 @@ final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Co

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

class InfixNamedArgDeprecation()(using Context) extends SyntaxMsg(DeprecatedNamedInfixArgID):
def msg(using Context) = "Named argument syntax is deprecated for infix application"
class AmbiguousNamedTupleInfixApply()(using Context) extends SyntaxMsg(AmbiguousNamedTupleInfixApplyID):
def msg(using Context) =
"Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list."
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)

def explain(using Context) =
i"""The argument will be parsed as a named tuple in future.
i"""Starting with Scala 3.6 infix named arguments are interpretted as Named Tuple.
|
|To avoid this warning, either remove the argument names or use dotted selection."""
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class CompilationTests {
compileFile("tests/rewrites/i20002.scala", defaultOptions.and("-indent", "-rewrite")),
compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")),
compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")),
compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
).checkRewrites()
}

Expand Down
34 changes: 27 additions & 7 deletions tests/neg/infix-named-args.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------
2 | def f = 42 + (x = 1) // error // a named tuple!
2 | def f = 42 + (x = 1) // error // werror
| ^^^^
| None of the overloaded alternatives of method + in class Int with types
| (x: Double): Double
Expand All @@ -11,11 +11,31 @@
| (x: Byte): Int
| (x: String): String
| match arguments ((x : Int)) (a named tuple)
-- [E007] Type Mismatch Error: tests/neg/infix-named-args.scala:13:18 --------------------------------------------------
13 | def g = this ** 2 // error
| ^
| Found: (2 : Int)
| Required: X
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 --------------------------------------------------------
2 | def f = 42 + (x = 1) // error // werror
| ^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:5:26 --------------------------------------------------------
5 | def g = new C() `multi` (x = 42, y = 27) // werror
| ^^^^^^^^^^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:6:21 --------------------------------------------------------
6 | def h = new C() ** (x = 42, y = 27) // werror
| ^^^^^^^^^^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:13:18 -------------------------------------------------------
13 | def f = this ** (x = 2) // werror
| ^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
there were 6 deprecation warnings; re-run with -deprecation for details
11 changes: 6 additions & 5 deletions tests/neg/infix-named-args.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
class C:
def f = 42 + (x = 1) // error // a named tuple!
def f = 42 + (x = 1) // error // werror
def multi(x: Int, y: Int): Int = x + y
def **(x: Int, y: Int): Int = x + y
def g = new C() `multi` (x = 42, y = 27) // werror // werror // not actually a tuple! appearances to the contrary
def h = new C() ** (x = 42, y = 27) // werror // werror
def g = new C() `multi` (x = 42, y = 27) // werror
def h = new C() ** (x = 42, y = 27) // werror

type X = (x: Int)

class D(d: Int):
def **(x: Int): Int = d * x
def **(x: X): Int = d * x.x
def f = this ** (x = 2)
def g = this ** 2 // error
def f = this ** (x = 2) // werror
def g = this ** 2
1 change: 0 additions & 1 deletion tests/neg/named-tuples.check
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,3 @@
| Required: (name : ?, age : ?)
|
| longer explanation available when compiling with `-explain`
there were 2 deprecation warnings; re-run with -deprecation for details
15 changes: 15 additions & 0 deletions tests/rewrites/infix-named-args.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class C:
def multi(x: Int, y: Int): Int = x + y
def **(x: Int, y: Int): Int = x + y
def g = new C().multi(x = 42, y = 27)
def h = new C().**(x = 42, y = 27)

type X = (x: Int)

class D(d: Int):
def **(x: Int): Int = d * x
def **(x: X): Int = d * x.x
def f = this.**(x = 2)
def g = this ** 2
def h = this ** ((x = 2))
def i = this.**(x = (1 + 1))
15 changes: 15 additions & 0 deletions tests/rewrites/infix-named-args.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class C:
def multi(x: Int, y: Int): Int = x + y
def **(x: Int, y: Int): Int = x + y
def g = new C() `multi` (x = 42, y = 27)
def h = new C() ** (x = 42, y = 27)

type X = (x: Int)

class D(d: Int):
def **(x: Int): Int = d * x
def **(x: X): Int = d * x.x
def f = this ** (x = 2)
def g = this ** 2
def h = this ** ((x = 2))
def i = this ** (x = (1 + 1))
14 changes: 14 additions & 0 deletions tests/warn/infix-named-args-migration.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//> using options -source:3.6-migration
class C:
def f = 42 + (x = 1) // warn // interpreted as 42.+(x = 1) under migration, x is a valid synthetic parameter name
def multi(x: Int, y: Int): Int = x + y
def **(x: Int, y: Int): Int = x + y
def g = new C() `multi` (x = 42, y = 27) // warn
def h = new C() ** (x = 42, y = 27) // warn

type X = (x: Int)

class D(d: Int):
def **(x: Int): Int = d * x
def f = this ** (x = 2) // warn
def g = this ** 2
7 changes: 0 additions & 7 deletions tests/warn/infix-named-args.scala

This file was deleted.

0 comments on commit f085145

Please sign in to comment.