Skip to content

Commit 10bb2e6

Browse files
Make named tuples an experimental feature again (#22045)
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}`
2 parents bcacaee + 24c3e7f commit 10bb2e6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+225
-125
lines changed

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

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ object Feature:
3434
val pureFunctions = experimental("pureFunctions")
3535
val captureChecking = experimental("captureChecking")
3636
val into = experimental("into")
37+
val namedTuples = experimental("namedTuples")
3738
val modularity = experimental("modularity")
3839
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
3940
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
@@ -65,6 +66,7 @@ object Feature:
6566
(pureFunctions, "Enable pure functions for capture checking"),
6667
(captureChecking, "Enable experimental capture checking"),
6768
(into, "Allow into modifier on parameter types"),
69+
(namedTuples, "Allow named tuples"),
6870
(modularity, "Enable experimental modularity features"),
6971
(betterMatchTypeExtractors, "Enable better match type extractors"),
7072
(betterFors, "Enable improvements in `for` comprehensions")

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +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)
29+
case AmbiguousNamedTupleSyntax extends MigrationVersion(`3.6`, future)
3030
case ImportWildcard extends MigrationVersion(future, future)
3131
case ImportRename extends MigrationVersion(future, future)
3232
case ParameterEnclosedByParenthesis extends MigrationVersion(future, future)

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ object Parsers {
667667
else leading :: Nil
668668

669669
def maybeNamed(op: () => Tree): () => Tree = () =>
670-
if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then
670+
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
671671
atSpan(in.offset):
672672
val name = ident()
673673
in.nextToken()
@@ -1149,8 +1149,8 @@ object Parsers {
11491149
if isType then infixOp
11501150
else infixOp.right match
11511151
case Tuple(args) if args.exists(_.isInstanceOf[NamedArg]) && !isNamedTupleOperator =>
1152-
report.errorOrMigrationWarning(AmbiguousNamedTupleInfixApply(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleInfixApply)
1153-
if MigrationVersion.AmbiguousNamedTupleInfixApply.needsPatch then
1152+
report.errorOrMigrationWarning(DeprecatedInfixNamedArgumentSyntax(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleSyntax)
1153+
if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then
11541154
val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), args)
11551155
patch(source, infixOp.span, asApply.show(using ctx.withoutColors))
11561156
asApply // allow to use pre-3.6 syntax in migration mode
@@ -2172,7 +2172,7 @@ object Parsers {
21722172

21732173
if namedOK && isIdent && in.lookahead.token == EQUALS then
21742174
commaSeparated(() => namedArgType())
2175-
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then
2175+
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
21762176
commaSeparated(() => namedElem())
21772177
else
21782178
commaSeparated(() => argType())

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
216216
case FinalLocalDefID // errorNumber: 200
217217
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
218218
case QuotedTypeMissingID // errorNumber: 202
219-
case AmbiguousNamedTupleAssignmentID // errorNumber: 203
220-
case AmbiguousNamedTupleInfixApplyID // errorNumber: 204
219+
case DeprecatedAssignmentSyntaxID // errorNumber: 203
220+
case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204
221221

222222
def errorNumber = ordinal - 1
223223

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

+9-10
Original file line numberDiff line numberDiff line change
@@ -3344,21 +3344,20 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q
33443344

33453345
end QuotedTypeMissing
33463346

3347-
final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(AmbiguousNamedTupleAssignmentID):
3347+
final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID):
33483348
override protected def msg(using Context): String =
3349-
i"""Ambiguous syntax: this is interpreted as a named tuple with one element,
3349+
i"""Deprecated syntax: in the future it would be interpreted as a named tuple with one element,
33503350
|not as an assignment.
33513351
|
33523352
|To assign a value, use curly braces: `{${key} = ${value}}`."""
3353-
3353+
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)
3354+
33543355
override protected def explain(using Context): String = ""
33553356

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

3361-
def explain(using Context) =
3362-
i"""Starting with Scala 3.6 infix named arguments are interpretted as Named Tuple.
3363-
|
3364-
|To avoid this warning, either remove the argument names or use dotted selection."""
3363+
def explain(using Context) = ""

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
795795
def tryNamedTupleSelection() =
796796
val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes
797797
val nameIdx = namedTupleElems.indexWhere(_._1 == selName)
798-
if nameIdx >= 0 && sourceVersion.isAtLeast(`3.6`) then
798+
if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then
799799
typed(
800800
untpd.Apply(
801801
untpd.Select(untpd.TypedSplice(qual), nme.apply),
@@ -3404,7 +3404,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34043404
/** Translate tuples of all arities */
34053405
def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree =
34063406
val tree1 = desugar.tuple(tree, pt)
3407-
checkAmbiguousNamedTupleAssignment(tree)
3407+
checkDeprecatedAssignmentSyntax(tree)
34083408
if tree1 ne tree then typed(tree1, pt)
34093409
else
34103410
val arity = tree.trees.length
@@ -3433,15 +3433,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
34333433
/** Checks if `tree` is a named tuple with one element that could be
34343434
* interpreted as an assignment, such as `(x = 1)`. If so, issues a warning.
34353435
*/
3436-
def checkAmbiguousNamedTupleAssignment(tree: untpd.Tuple)(using Context): Unit =
3436+
def checkDeprecatedAssignmentSyntax(tree: untpd.Tuple)(using Context): Unit =
34373437
tree.trees match
34383438
case List(NamedArg(name, value)) =>
34393439
val tmpCtx = ctx.fresh.setNewTyperState()
34403440
typedAssign(untpd.Assign(untpd.Ident(name), value), WildcardType)(using tmpCtx)
34413441
if !tmpCtx.reporter.hasErrors then
34423442
// If there are no errors typing the above, then the named tuple is
34433443
// ambiguous and we issue a warning.
3444-
report.migrationWarning(AmbiguousNamedTupleAssignment(name, value), tree.srcPos)
3444+
report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos)
3445+
if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then
3446+
patch(tree.source, Span(tree.span.start, tree.span.start + 1), "{")
3447+
patch(tree.source, Span(tree.span.end - 1, tree.span.end), "}")
34453448
case _ => ()
34463449

34473450
/** Retrieve symbol attached to given tree */

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

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class CompilationTests {
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")),
8282
compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
83+
compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
8384
).checkRewrites()
8485
}
8586

docs/_docs/reference/other-new-features/named-tuples.md renamed to docs/_docs/reference/experimental/named-tuples.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
layout: doc-page
33
title: "Named Tuples"
4-
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html
4+
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html
55
---
66

7-
Starting in Scala 3.6, the elements of a tuple can be named. Example:
7+
The elements of a tuple can now be named. Example:
88
```scala
99
type Person = (name: String, age: Int)
1010
val Bob: Person = (name = "Bob", age = 33)

docs/sidebar.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ subsection:
7272
- page: reference/other-new-features/export.md
7373
- page: reference/other-new-features/opaques.md
7474
- page: reference/other-new-features/opaques-details.md
75-
- page: reference/other-new-features/named-tuples.md
7675
- page: reference/other-new-features/open-classes.md
7776
- page: reference/other-new-features/parameter-untupling.md
7877
- page: reference/other-new-features/parameter-untupling-spec.md
@@ -159,6 +158,7 @@ subsection:
159158
- page: reference/experimental/cc.md
160159
- page: reference/experimental/purefuns.md
161160
- page: reference/experimental/tupled-function.md
161+
- page: reference/experimental/named-tuples.md
162162
- page: reference/experimental/modularity.md
163163
- page: reference/experimental/typeclasses.md
164164
- page: reference/experimental/runtimeChecked.md

library/src/scala/NamedTuple.scala

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package scala
2+
import annotation.experimental
23
import compiletime.ops.boolean.*
34

5+
@experimental
46
object NamedTuple:
57

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

133135
/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
136+
@experimental
134137
object NamedTupleDecomposition:
135138
import NamedTuple.*
136139
extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V])

library/src/scala/runtime/stdLibPatches/language.scala

-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ object language:
9797
* @see [[https://dotty.epfl.ch/docs/reference/experimental/named-tuples]]
9898
*/
9999
@compileTimeOnly("`namedTuples` can only be used at compile time in import statements")
100-
@deprecated("The experimental.namedTuples language import is no longer needed since the feature is now standard", since = "3.6")
101100
object namedTuples
102101

103102
/** Experimental support for new features for better modularity, including

presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -1988,7 +1988,8 @@ class CompletionSuite extends BaseCompletionSuite:
19881988

19891989
@Test def `namedTuple completions` =
19901990
check(
1991-
"""|import scala.NamedTuple.*
1991+
"""|import scala.language.experimental.namedTuples
1992+
|import scala.NamedTuple.*
19921993
|
19931994
|val person = (name = "Jamie", city = "Lausanne")
19941995
|
@@ -1999,7 +2000,8 @@ class CompletionSuite extends BaseCompletionSuite:
19992000

20002001
@Test def `Selectable with namedTuple Fields member` =
20012002
check(
2002-
"""|import scala.NamedTuple.*
2003+
"""|import scala.language.experimental.namedTuples
2004+
|import scala.NamedTuple.*
20032005
|
20042006
|class NamedTupleSelectable extends Selectable {
20052007
| type Fields <: AnyNamedTuple

tests/neg/i20517.check

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:9:43 -------------------------------------------------------------
2-
9 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
3-
| ^^^^^^^^^^^
4-
| Found: (elem : String)
5-
| Required: NamedTuple.From[(foo : Foo[Any])]
6-
|
7-
| longer explanation available when compiling with `-explain`
1+
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------
2+
10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
3+
| ^^^^^^^^^^^
4+
| Found: (elem : String)
5+
| Required: NamedTuple.From[(foo : Foo[Any])]
6+
|
7+
| longer explanation available when compiling with `-explain`

tests/neg/i20517.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import scala.language.experimental.namedTuples
12
import NamedTuple.From
23

34
case class Foo[+T](elem: T)

tests/neg/infix-named-args.check

+18-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
-- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------
2-
2 | def f = 42 + (x = 1) // error // werror
1+
-- [E134] Type Error: tests/neg/infix-named-args.scala:4:13 ------------------------------------------------------------
2+
4 | 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,31 +11,27 @@
1111
| (x: Byte): Int
1212
| (x: String): String
1313
| match arguments ((x : Int)) (a named tuple)
14-
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 --------------------------------------------------------
15-
2 | def f = 42 + (x = 1) // error // werror
14+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:4:15 --------------------------------------------------------
15+
4 | def f = 42 + (x = 1) // error // werror
1616
| ^^^^^^^
17-
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
17+
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
18+
|To avoid this warning, either remove the argument names or use dotted selection.
1819
|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
20+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:7:26 --------------------------------------------------------
21+
7 | def g = new C() `multi` (x = 42, y = 27) // werror
2322
| ^^^^^^^^^^^^^^^^
24-
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
23+
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
24+
|To avoid this warning, either remove the argument names or use dotted selection.
2525
|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
26+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:8:21 --------------------------------------------------------
27+
8 | def h = new C() ** (x = 42, y = 27) // werror
3028
| ^^^^^^^^^^^^^^^^
31-
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
29+
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
30+
|To avoid this warning, either remove the argument names or use dotted selection.
3231
|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
32+
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:15:18 -------------------------------------------------------
33+
15 | def f = this ** (x = 2) // werror
3734
| ^^^^^^^
38-
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
35+
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
36+
|To avoid this warning, either remove the argument names or use dotted selection.
3937
|This can be rewritten automatically under -rewrite -source 3.6-migration.
40-
|
41-
| longer explanation available when compiling with `-explain`

tests/neg/infix-named-args.scala

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import scala.language.experimental.namedTuples
2+
13
class C:
24
def f = 42 + (x = 1) // error // werror
35
def multi(x: Int, y: Int): Int = x + y

tests/neg/named-tuple-selectable.scala

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import scala.language.experimental.namedTuples
2+
13
class FromFields extends Selectable:
24
type Fields = (i: Int)
35
def selectDynamic(key: String) =

tests/neg/named-tuples-2.check

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
-- Error: tests/neg/named-tuples-2.scala:4:9 ---------------------------------------------------------------------------
2-
4 | case (name, age) => () // error
1+
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
2+
5 | case (name, age) => () // error
33
| ^
44
| this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple2
5-
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
6-
5 | case (n, a, m, x) => () // error
5+
-- Error: tests/neg/named-tuples-2.scala:6:9 ---------------------------------------------------------------------------
6+
6 | case (n, a, m, x) => () // error
77
| ^
88
| this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple4

tests/neg/named-tuples-2.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import language.experimental.namedTuples
12
def Test =
23
val person = (name = "Bob", age = 33, married = true)
34
person match

tests/neg/named-tuples-3.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 -----------------------------------------------------
2-
5 |val p: Person = f // error
1+
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:7:16 -----------------------------------------------------
2+
7 |val p: Person = f // error
33
| ^
44
| Found: NamedTuple.NamedTuple[(Int, Any), (Int, String)]
55
| Required: Person

tests/neg/named-tuples-3.scala

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import language.experimental.namedTuples
2+
13
def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ???
24

35
type Person = (name: Int, age: String)

0 commit comments

Comments
 (0)