Skip to content

Commit

Permalink
Make named tuples an experimental feature again (#22045)
Browse files Browse the repository at this point in the history
Resolves #22042

* Reverts most of the stabilization changes introduced in
#21680 excluding bugfixes introduced
when stabilizing the name tuples
* Adapts #21823 and
#21949 warnings to make them both
syntax deprecations instead of ambiguous syntax.
* Adds automatic rewrite to #21823
to replace `(foo = bar)` into `{foo = bar}`
  • Loading branch information
WojciechMazur authored Nov 28, 2024
2 parents bcacaee + 24c3e7f commit 10bb2e6
Show file tree
Hide file tree
Showing 57 changed files with 225 additions and 125 deletions.
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ object Feature:
val pureFunctions = experimental("pureFunctions")
val captureChecking = experimental("captureChecking")
val into = experimental("into")
val namedTuples = experimental("namedTuples")
val modularity = experimental("modularity")
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
Expand Down Expand Up @@ -65,6 +66,7 @@ object Feature:
(pureFunctions, "Enable pure functions for capture checking"),
(captureChecking, "Enable experimental capture checking"),
(into, "Allow into modifier on parameter types"),
(namedTuples, "Allow named tuples"),
(modularity, "Enable experimental modularity features"),
(betterMatchTypeExtractors, "Enable better match type extractors"),
(betterFors, "Enable improvements in `for` comprehensions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +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 AmbiguousNamedTupleSyntax extends MigrationVersion(`3.6`, future)
case ImportWildcard extends MigrationVersion(future, future)
case ImportRename extends MigrationVersion(future, future)
case ParameterEnclosedByParenthesis extends MigrationVersion(future, future)
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ object Parsers {
else leading :: Nil

def maybeNamed(op: () => Tree): () => Tree = () =>
if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
atSpan(in.offset):
val name = ident()
in.nextToken()
Expand Down Expand Up @@ -1149,8 +1149,8 @@ object Parsers {
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
report.errorOrMigrationWarning(DeprecatedInfixNamedArgumentSyntax(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleSyntax)
if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then
val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), args)
patch(source, infixOp.span, asApply.show(using ctx.withoutColors))
asApply // allow to use pre-3.6 syntax in migration mode
Expand Down Expand Up @@ -2172,7 +2172,7 @@ object Parsers {

if namedOK && isIdent && in.lookahead.token == EQUALS then
commaSeparated(() => namedArgType())
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
commaSeparated(() => namedElem())
else
commaSeparated(() => argType())
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case FinalLocalDefID // errorNumber: 200
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
case QuotedTypeMissingID // errorNumber: 202
case AmbiguousNamedTupleAssignmentID // errorNumber: 203
case AmbiguousNamedTupleInfixApplyID // errorNumber: 204
case DeprecatedAssignmentSyntaxID // errorNumber: 203
case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204

def errorNumber = ordinal - 1

Expand Down
19 changes: 9 additions & 10 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3344,21 +3344,20 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q

end QuotedTypeMissing

final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(AmbiguousNamedTupleAssignmentID):
final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID):
override protected def msg(using Context): String =
i"""Ambiguous syntax: this is interpreted as a named tuple with one element,
i"""Deprecated syntax: in the future it would be interpreted as a named tuple with one element,
|not as an assignment.
|
|To assign a value, use curly braces: `{${key} = ${value}}`."""

+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)

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

class AmbiguousNamedTupleInfixApply()(using Context) extends SyntaxMsg(AmbiguousNamedTupleInfixApplyID):
class DeprecatedInfixNamedArgumentSyntax()(using Context) extends SyntaxMsg(DeprecatedInfixNamedArgumentSyntaxID):
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`)
i"""Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection."""
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)

def explain(using Context) =
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."""
def explain(using Context) = ""
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def tryNamedTupleSelection() =
val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes
val nameIdx = namedTupleElems.indexWhere(_._1 == selName)
if nameIdx >= 0 && sourceVersion.isAtLeast(`3.6`) then
if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then
typed(
untpd.Apply(
untpd.Select(untpd.TypedSplice(qual), nme.apply),
Expand Down Expand Up @@ -3404,7 +3404,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** Translate tuples of all arities */
def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree =
val tree1 = desugar.tuple(tree, pt)
checkAmbiguousNamedTupleAssignment(tree)
checkDeprecatedAssignmentSyntax(tree)
if tree1 ne tree then typed(tree1, pt)
else
val arity = tree.trees.length
Expand Down Expand Up @@ -3433,15 +3433,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** Checks if `tree` is a named tuple with one element that could be
* interpreted as an assignment, such as `(x = 1)`. If so, issues a warning.
*/
def checkAmbiguousNamedTupleAssignment(tree: untpd.Tuple)(using Context): Unit =
def checkDeprecatedAssignmentSyntax(tree: untpd.Tuple)(using Context): Unit =
tree.trees match
case List(NamedArg(name, value)) =>
val tmpCtx = ctx.fresh.setNewTyperState()
typedAssign(untpd.Assign(untpd.Ident(name), value), WildcardType)(using tmpCtx)
if !tmpCtx.reporter.hasErrors then
// If there are no errors typing the above, then the named tuple is
// ambiguous and we issue a warning.
report.migrationWarning(AmbiguousNamedTupleAssignment(name, value), tree.srcPos)
report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos)
if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then
patch(tree.source, Span(tree.span.start, tree.span.start + 1), "{")
patch(tree.source, Span(tree.span.end - 1, tree.span.end), "}")
case _ => ()

/** Retrieve symbol attached to given tree */
Expand Down
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 @@ -80,6 +80,7 @@ class CompilationTests {
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")),
compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
).checkRewrites()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
layout: doc-page
title: "Named Tuples"
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html
---

Starting in Scala 3.6, the elements of a tuple can be named. Example:
The elements of a tuple can now be named. Example:
```scala
type Person = (name: String, age: Int)
val Bob: Person = (name = "Bob", age = 33)
Expand Down
2 changes: 1 addition & 1 deletion docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ subsection:
- page: reference/other-new-features/export.md
- page: reference/other-new-features/opaques.md
- page: reference/other-new-features/opaques-details.md
- page: reference/other-new-features/named-tuples.md
- page: reference/other-new-features/open-classes.md
- page: reference/other-new-features/parameter-untupling.md
- page: reference/other-new-features/parameter-untupling-spec.md
Expand Down Expand Up @@ -159,6 +158,7 @@ subsection:
- page: reference/experimental/cc.md
- page: reference/experimental/purefuns.md
- page: reference/experimental/tupled-function.md
- page: reference/experimental/named-tuples.md
- page: reference/experimental/modularity.md
- page: reference/experimental/typeclasses.md
- page: reference/experimental/runtimeChecked.md
Expand Down
3 changes: 3 additions & 0 deletions library/src/scala/NamedTuple.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package scala
import annotation.experimental
import compiletime.ops.boolean.*

@experimental
object NamedTuple:

/** The type to which named tuples get mapped to. For instance,
Expand Down Expand Up @@ -131,6 +133,7 @@ object NamedTuple:
end NamedTuple

/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
@experimental
object NamedTupleDecomposition:
import NamedTuple.*
extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V])
Expand Down
1 change: 0 additions & 1 deletion library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ object language:
* @see [[https://dotty.epfl.ch/docs/reference/experimental/named-tuples]]
*/
@compileTimeOnly("`namedTuples` can only be used at compile time in import statements")
@deprecated("The experimental.namedTuples language import is no longer needed since the feature is now standard", since = "3.6")
object namedTuples

/** Experimental support for new features for better modularity, including
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1988,7 +1988,8 @@ class CompletionSuite extends BaseCompletionSuite:

@Test def `namedTuple completions` =
check(
"""|import scala.NamedTuple.*
"""|import scala.language.experimental.namedTuples
|import scala.NamedTuple.*
|
|val person = (name = "Jamie", city = "Lausanne")
|
Expand All @@ -1999,7 +2000,8 @@ class CompletionSuite extends BaseCompletionSuite:

@Test def `Selectable with namedTuple Fields member` =
check(
"""|import scala.NamedTuple.*
"""|import scala.language.experimental.namedTuples
|import scala.NamedTuple.*
|
|class NamedTupleSelectable extends Selectable {
| type Fields <: AnyNamedTuple
Expand Down
14 changes: 7 additions & 7 deletions tests/neg/i20517.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:9:43 -------------------------------------------------------------
9 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
| ^^^^^^^^^^^
| Found: (elem : String)
| Required: NamedTuple.From[(foo : Foo[Any])]
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------
10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
| ^^^^^^^^^^^
| Found: (elem : String)
| Required: NamedTuple.From[(foo : Foo[Any])]
|
| longer explanation available when compiling with `-explain`
1 change: 1 addition & 0 deletions tests/neg/i20517.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.language.experimental.namedTuples
import NamedTuple.From

case class Foo[+T](elem: T)
Expand Down
40 changes: 18 additions & 22 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 // werror
-- [E134] Type Error: tests/neg/infix-named-args.scala:4:13 ------------------------------------------------------------
4 | 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,31 +11,27 @@
| (x: Byte): Int
| (x: String): String
| match arguments ((x : Int)) (a named tuple)
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 --------------------------------------------------------
2 | def f = 42 + (x = 1) // error // werror
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:4:15 --------------------------------------------------------
4 | 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.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|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
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:7:26 --------------------------------------------------------
7 | 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.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|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
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:8:21 --------------------------------------------------------
8 | 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.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|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
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:15:18 -------------------------------------------------------
15 | 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.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
2 changes: 2 additions & 0 deletions tests/neg/infix-named-args.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.language.experimental.namedTuples

class C:
def f = 42 + (x = 1) // error // werror
def multi(x: Int, y: Int): Int = x + y
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/named-tuple-selectable.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.language.experimental.namedTuples

class FromFields extends Selectable:
type Fields = (i: Int)
def selectDynamic(key: String) =
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/named-tuples-2.check
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-- Error: tests/neg/named-tuples-2.scala:4:9 ---------------------------------------------------------------------------
4 | case (name, age) => () // error
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
5 | case (name, age) => () // error
| ^
| this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple2
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
5 | case (n, a, m, x) => () // error
-- Error: tests/neg/named-tuples-2.scala:6:9 ---------------------------------------------------------------------------
6 | case (n, a, m, x) => () // error
| ^
| this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple4
1 change: 1 addition & 0 deletions tests/neg/named-tuples-2.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.experimental.namedTuples
def Test =
val person = (name = "Bob", age = 33, married = true)
person match
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/named-tuples-3.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 -----------------------------------------------------
5 |val p: Person = f // error
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:7:16 -----------------------------------------------------
7 |val p: Person = f // error
| ^
| Found: NamedTuple.NamedTuple[(Int, Any), (Int, String)]
| Required: Person
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/named-tuples-3.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import language.experimental.namedTuples

def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ???

type Person = (name: Int, age: String)
Expand Down
Loading

0 comments on commit 10bb2e6

Please sign in to comment.