diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 6390d8d32d3f..84bc4fec169f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1104,9 +1104,14 @@ object Parsers { if (prec < opPrec || leftAssoc && prec == opPrec) { opStack = opStack.tail recur { - atSpan(opInfo.operator.span union opInfo.operand.span union top.span) { + 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) - } } } else top diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 6d0a85b3ef0f..c959028a880f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -217,6 +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 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 3b7fba1cb52d..c678dd95beaf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3352,3 +3352,10 @@ final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Co |To assign a value, use curly braces: `{${key} = ${value}}`.""" 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" + def explain(using Context) = + i"""The argument will be parsed as a named tuple in future. + | + |To avoid this warning, either remove the argument names or use dotted selection.""" diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 68143dfd2ba0..3bcfba47aac6 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -110,7 +110,13 @@ object ErrorReporting { case tp => i" and expected result type $tp" } i"(${tp.typedArgs().tpes}%, %)$result" - s"arguments ${argStr(tp)}" + def hasNames = tp.args.exists: + case tree: untpd.Tuple => tree.trees.exists: + case NamedArg(_, _) => true + case _ => false + case _ => false + val addendum = if hasNames then " (a named tuple)" else "" + s"arguments ${argStr(tp)}$addendum" case _ => i"expected type $tp" } diff --git a/docs/_docs/reference/other-new-features/named-tuples.md b/docs/_docs/reference/other-new-features/named-tuples.md index df96a91fe182..5483c5cc255b 100644 --- a/docs/_docs/reference/other-new-features/named-tuples.md +++ b/docs/_docs/reference/other-new-features/named-tuples.md @@ -17,8 +17,9 @@ val persons: List[Person] = ... val minors = persons.filter: p => p.age < 18 ``` -Named bindings in tuples are similar to function parameters and arguments. We use `name: Type` for element types and `name = value` for element values. It is illegal to mix named and unnamed elements in a tuple, or to use the same same -name for two different elements. +Named bindings in tuples are similar to function parameters and arguments. +We use `name: Type` for element types and `name = value` for element values. +It is illegal to mix named and unnamed elements in a tuple, or to use the same name for two different elements. Fields of named tuples can be selected by their name, as in the line `p.age < 18` above. diff --git a/tests/neg/infix-named-args.check b/tests/neg/infix-named-args.check new file mode 100644 index 000000000000..86a98bf9b3d6 --- /dev/null +++ b/tests/neg/infix-named-args.check @@ -0,0 +1,21 @@ +-- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------ +2 | def f = 42 + (x = 1) // error // a named tuple! + | ^^^^ + | None of the overloaded alternatives of method + in class Int with types + | (x: Double): Double + | (x: Float): Float + | (x: Long): Long + | (x: Int): Int + | (x: Char): Int + | (x: Short): Int + | (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 + | + | longer explanation available when compiling with `-explain` +there were 6 deprecation warnings; re-run with -deprecation for details diff --git a/tests/neg/infix-named-args.scala b/tests/neg/infix-named-args.scala new file mode 100644 index 000000000000..2cec30a5d0ff --- /dev/null +++ b/tests/neg/infix-named-args.scala @@ -0,0 +1,13 @@ +class C: + def f = 42 + (x = 1) // error // a named tuple! + 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 + +type X = (x: Int) + +class D(d: Int): + def **(x: X): Int = d * x.x + def f = this ** (x = 2) + def g = this ** 2 // error diff --git a/tests/neg/named-tuples.check b/tests/neg/named-tuples.check index 8ec958b6a75d..a66bd3e52039 100644 --- a/tests/neg/named-tuples.check +++ b/tests/neg/named-tuples.check @@ -101,3 +101,4 @@ | Required: (name : ?, age : ?) | | longer explanation available when compiling with `-explain` +there were 2 deprecation warnings; re-run with -deprecation for details diff --git a/tests/warn/infix-named-args.scala b/tests/warn/infix-named-args.scala new file mode 100644 index 000000000000..2e7803cf2720 --- /dev/null +++ b/tests/warn/infix-named-args.scala @@ -0,0 +1,7 @@ +//> using options -deprecation +type X = (x: Int) + +class E(e: Int): + def **(x: Int): Int = e * x + def **(x: X): Int = e * x.x + def f = this ** (x = 2) // warn