Skip to content

Commit 87cdbc8

Browse files
committed
Make named tuples a standard feature
- Deprecate experimental language import - Make named tuple features conditional on -source >= 3.6 instead - Make the NamedTuple object non-experimental. - Move NamedTuple it to src-bootstrapped since it relies on clause interleaving which is only standard in 3.6 as well. - Drop the experimental.namedTuple import from tests
1 parent 56a384f commit 87cdbc8

36 files changed

+70
-112
lines changed

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

-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ object Feature:
3434
val pureFunctions = experimental("pureFunctions")
3535
val captureChecking = experimental("captureChecking")
3636
val into = experimental("into")
37-
val namedTuples = experimental("namedTuples")
3837
val modularity = experimental("modularity")
3938
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
4039
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
@@ -66,7 +65,6 @@ object Feature:
6665
(pureFunctions, "Enable pure functions for capture checking"),
6766
(captureChecking, "Enable experimental capture checking"),
6867
(into, "Allow into modifier on parameter types"),
69-
(namedTuples, "Allow named tuples"),
7068
(modularity, "Enable experimental modularity features"),
7169
(betterMatchTypeExtractors, "Enable better match type extractors"),
7270
(betterFors, "Enable improvements in `for` comprehensions")

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ object ScalaSettingsProperties:
2525
ScalaRelease.values.toList.map(_.show)
2626

2727
def supportedSourceVersions: List[String] =
28-
SourceVersion.values.toList.map(_.toString)
28+
(SourceVersion.values.toList.diff(SourceVersion.illegalSourceVersionNames)).toList.map(_.toString)
2929

3030
def supportedLanguageFeatures: List[ChoiceWithHelp[String]] =
3131
Feature.values.map((n, d) => ChoiceWithHelp(n.toString, d))

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ object Parsers {
651651
else leading :: Nil
652652

653653
def maybeNamed(op: () => Tree): () => Tree = () =>
654-
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
654+
if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then
655655
atSpan(in.offset):
656656
val name = ident()
657657
in.nextToken()
@@ -2137,7 +2137,7 @@ object Parsers {
21372137

21382138
if namedOK && isIdent && in.lookahead.token == EQUALS then
21392139
commaSeparated(() => namedArgType())
2140-
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
2140+
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then
21412141
commaSeparated(() => namedElem())
21422142
else
21432143
commaSeparated(() => argType())

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
789789
def tryNamedTupleSelection() =
790790
val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes
791791
val nameIdx = namedTupleElems.indexWhere(_._1 == selName)
792-
if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then
792+
if nameIdx >= 0 && sourceVersion.isAtLeast(`3.6`) then
793793
typed(
794794
untpd.Apply(
795795
untpd.Select(untpd.TypedSplice(qual), nme.apply),

library/src/scala/NamedTuple.scala renamed to library/src-bootstrapped/scala/NamedTuple.scala

-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package scala
2-
import scala.language.experimental.clauseInterleaving
3-
import annotation.experimental
42
import compiletime.ops.boolean.*
53

6-
@experimental
74
object NamedTuple:
85

96
/** The type to which named tuples get mapped to. For instance,
@@ -133,7 +130,6 @@ object NamedTuple:
133130
end NamedTuple
134131

135132
/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
136-
@experimental
137133
object NamedTupleDecomposition:
138134
import NamedTuple.*
139135
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,6 +97,7 @@ 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")
100101
object namedTuples
101102

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

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

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

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

20012000
@Test def `Selectable with namedTuple Fields member` =
20022001
check(
2003-
"""|import scala.language.experimental.namedTuples
2004-
|import scala.NamedTuple.*
2002+
"""|import scala.NamedTuple.*
20052003
|
20062004
|class NamedTupleSelectable extends Selectable {
20072005
| type Fields <: AnyNamedTuple
@@ -2091,7 +2089,7 @@ class CompletionSuite extends BaseCompletionSuite:
20912089
|""".stripMargin
20922090
)
20932091

2094-
@Test def `conflict-3` =
2092+
@Test def `conflict-3` =
20952093
check(
20962094
"""|package a
20972095
|object A {

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: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`
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`

tests/neg/i20517.scala

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

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

tests/neg/named-tuple-selectable.scala

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import scala.language.experimental.namedTuples
2-
31
class FromFields extends Selectable:
42
type Fields = (i: Int)
53
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:5:9 ---------------------------------------------------------------------------
2-
5 | case (name, age) => () // error
1+
-- Error: tests/neg/named-tuples-2.scala:4:9 ---------------------------------------------------------------------------
2+
4 | 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:6:9 ---------------------------------------------------------------------------
6-
6 | case (n, a, m, x) => () // error
5+
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
6+
5 | 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,4 +1,3 @@
1-
import language.experimental.namedTuples
21
def Test =
32
val person = (name = "Bob", age = 33, married = true)
43
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:7:16 -----------------------------------------------------
2-
7 |val p: Person = f // error
1+
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 -----------------------------------------------------
2+
5 |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,5 +1,3 @@
1-
import language.experimental.namedTuples
2-
31
def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ???
42

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

tests/neg/named-tuples.check

+46-46
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,101 @@
1-
-- Error: tests/neg/named-tuples.scala:9:19 ----------------------------------------------------------------------------
2-
9 | val illformed = (_2 = 2) // error
1+
-- Error: tests/neg/named-tuples.scala:8:19 ----------------------------------------------------------------------------
2+
8 | val illformed = (_2 = 2) // error
33
| ^^^^^^
44
| _2 cannot be used as the name of a tuple element because it is a regular tuple selector
5-
-- Error: tests/neg/named-tuples.scala:10:20 ---------------------------------------------------------------------------
6-
10 | type Illformed = (_1: Int) // error
7-
| ^^^^^^^
8-
| _1 cannot be used as the name of a tuple element because it is a regular tuple selector
9-
-- Error: tests/neg/named-tuples.scala:11:40 ---------------------------------------------------------------------------
10-
11 | val illformed2 = (name = "", age = 0, name = true) // error
5+
-- Error: tests/neg/named-tuples.scala:9:20 ----------------------------------------------------------------------------
6+
9 | type Illformed = (_1: Int) // error
7+
| ^^^^^^^
8+
| _1 cannot be used as the name of a tuple element because it is a regular tuple selector
9+
-- Error: tests/neg/named-tuples.scala:10:40 ---------------------------------------------------------------------------
10+
10 | val illformed2 = (name = "", age = 0, name = true) // error
1111
| ^^^^^^^^^^^
1212
| Duplicate tuple element name
13-
-- Error: tests/neg/named-tuples.scala:12:45 ---------------------------------------------------------------------------
14-
12 | type Illformed2 = (name: String, age: Int, name: Boolean) // error
13+
-- Error: tests/neg/named-tuples.scala:11:45 ---------------------------------------------------------------------------
14+
11 | type Illformed2 = (name: String, age: Int, name: Boolean) // error
1515
| ^^^^^^^^^^^^^
1616
| Duplicate tuple element name
17-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:20 ------------------------------------------------------
18-
20 | val _: NameOnly = person // error
17+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:19:20 ------------------------------------------------------
18+
19 | val _: NameOnly = person // error
1919
| ^^^^^^
2020
| Found: (Test.person : (name : String, age : Int))
2121
| Required: Test.NameOnly
2222
|
2323
| longer explanation available when compiling with `-explain`
24-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:21:18 ------------------------------------------------------
25-
21 | val _: Person = nameOnly // error
24+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:18 ------------------------------------------------------
25+
20 | val _: Person = nameOnly // error
2626
| ^^^^^^^^
2727
| Found: (Test.nameOnly : (name : String))
2828
| Required: Test.Person
2929
|
3030
| longer explanation available when compiling with `-explain`
31-
-- [E172] Type Error: tests/neg/named-tuples.scala:22:41 ---------------------------------------------------------------
32-
22 | val _: Person = (name = "") ++ nameOnly // error
31+
-- [E172] Type Error: tests/neg/named-tuples.scala:21:41 ---------------------------------------------------------------
32+
21 | val _: Person = (name = "") ++ nameOnly // error
3333
| ^
3434
| Cannot prove that Tuple.Disjoint[Tuple1[("name" : String)], Tuple1[("name" : String)]] =:= (true : Boolean).
35-
-- [E008] Not Found Error: tests/neg/named-tuples.scala:23:9 -----------------------------------------------------------
36-
23 | person._1 // error
35+
-- [E008] Not Found Error: tests/neg/named-tuples.scala:22:9 -----------------------------------------------------------
36+
22 | person._1 // error
3737
| ^^^^^^^^^
3838
| value _1 is not a member of (name : String, age : Int)
39-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:25:36 ------------------------------------------------------
40-
25 | val _: (age: Int, name: String) = person // error
39+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:24:36 ------------------------------------------------------
40+
24 | val _: (age: Int, name: String) = person // error
4141
| ^^^^^^
4242
| Found: (Test.person : (name : String, age : Int))
4343
| Required: (age : Int, name : String)
4444
|
4545
| longer explanation available when compiling with `-explain`
46-
-- Error: tests/neg/named-tuples.scala:27:17 ---------------------------------------------------------------------------
47-
27 | val (name = x, agee = y) = person // error
46+
-- Error: tests/neg/named-tuples.scala:26:17 ---------------------------------------------------------------------------
47+
26 | val (name = x, agee = y) = person // error
4848
| ^^^^^^^^
4949
| No element named `agee` is defined in selector type (name : String, age : Int)
50-
-- Error: tests/neg/named-tuples.scala:30:10 ---------------------------------------------------------------------------
51-
30 | case (name = n, age = a) => () // error // error
50+
-- Error: tests/neg/named-tuples.scala:29:10 ---------------------------------------------------------------------------
51+
29 | case (name = n, age = a) => () // error // error
5252
| ^^^^^^^^
5353
| No element named `name` is defined in selector type (String, Int)
54-
-- Error: tests/neg/named-tuples.scala:30:20 ---------------------------------------------------------------------------
55-
30 | case (name = n, age = a) => () // error // error
54+
-- Error: tests/neg/named-tuples.scala:29:20 ---------------------------------------------------------------------------
55+
29 | case (name = n, age = a) => () // error // error
5656
| ^^^^^^^
5757
| No element named `age` is defined in selector type (String, Int)
58-
-- [E172] Type Error: tests/neg/named-tuples.scala:32:27 ---------------------------------------------------------------
59-
32 | val pp = person ++ (1, 2) // error
58+
-- [E172] Type Error: tests/neg/named-tuples.scala:31:27 ---------------------------------------------------------------
59+
31 | val pp = person ++ (1, 2) // error
6060
| ^
6161
| Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean).
62-
-- [E172] Type Error: tests/neg/named-tuples.scala:35:18 ---------------------------------------------------------------
63-
35 | person ++ (1, 2) match // error
62+
-- [E172] Type Error: tests/neg/named-tuples.scala:34:18 ---------------------------------------------------------------
63+
34 | person ++ (1, 2) match // error
6464
| ^
6565
| Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean).
66-
-- Error: tests/neg/named-tuples.scala:38:17 ---------------------------------------------------------------------------
67-
38 | val bad = ("", age = 10) // error
66+
-- Error: tests/neg/named-tuples.scala:37:17 ---------------------------------------------------------------------------
67+
37 | val bad = ("", age = 10) // error
6868
| ^^^^^^^^
6969
| Illegal combination of named and unnamed tuple elements
70-
-- Error: tests/neg/named-tuples.scala:41:20 ---------------------------------------------------------------------------
71-
41 | case (name = n, age) => () // error
70+
-- Error: tests/neg/named-tuples.scala:40:20 ---------------------------------------------------------------------------
71+
40 | case (name = n, age) => () // error
7272
| ^^^
7373
| Illegal combination of named and unnamed tuple elements
74-
-- Error: tests/neg/named-tuples.scala:42:16 ---------------------------------------------------------------------------
75-
42 | case (name, age = a) => () // error
74+
-- Error: tests/neg/named-tuples.scala:41:16 ---------------------------------------------------------------------------
75+
41 | case (name, age = a) => () // error
7676
| ^^^^^^^
7777
| Illegal combination of named and unnamed tuple elements
78-
-- Error: tests/neg/named-tuples.scala:45:10 ---------------------------------------------------------------------------
79-
45 | case (age = x) => // error
78+
-- Error: tests/neg/named-tuples.scala:44:10 ---------------------------------------------------------------------------
79+
44 | case (age = x) => // error
8080
| ^^^^^^^
8181
| No element named `age` is defined in selector type Tuple
82-
-- [E172] Type Error: tests/neg/named-tuples.scala:47:27 ---------------------------------------------------------------
83-
47 | val p2 = person ++ person // error
82+
-- [E172] Type Error: tests/neg/named-tuples.scala:46:27 ---------------------------------------------------------------
83+
46 | val p2 = person ++ person // error
8484
| ^
8585
|Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("name" : String), ("age" : String))] =:= (true : Boolean).
86-
-- [E172] Type Error: tests/neg/named-tuples.scala:48:43 ---------------------------------------------------------------
87-
48 | val p3 = person ++ (first = 11, age = 33) // error
86+
-- [E172] Type Error: tests/neg/named-tuples.scala:47:43 ---------------------------------------------------------------
87+
47 | val p3 = person ++ (first = 11, age = 33) // error
8888
| ^
8989
|Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("first" : String), ("age" : String))] =:= (true : Boolean).
90-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:50:22 ------------------------------------------------------
91-
50 | val p5 = person.zip((first = 11, age = 33)) // error
90+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:49:22 ------------------------------------------------------
91+
49 | val p5 = person.zip((first = 11, age = 33)) // error
9292
| ^^^^^^^^^^^^^^^^^^^^^^
9393
| Found: (first : Int, age : Int)
9494
| Required: NamedTuple.NamedTuple[(("name" : String), ("age" : String)), Tuple]
9595
|
9696
| longer explanation available when compiling with `-explain`
97-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:61:32 ------------------------------------------------------
98-
61 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error
97+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:60:32 ------------------------------------------------------
98+
60 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error
9999
| ^^^^^^^^^^^^^^^^^^^^^
100100
| Found: (name : String, ag : Int)
101101
| Required: (name : ?, age : ?)

tests/neg/named-tuples.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import annotation.experimental
2-
import language.experimental.namedTuples
32

4-
@experimental object Test:
3+
object Test:
54

65
type Person = (name: String, age: Int)
76
val person = (name = "Bob", age = 33): (name: String, age: Int)

tests/new/test.scala

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import language.experimental.namedTuples
2-
31
type Person = (name: String, age: Int)
42

53
trait A:

tests/pos/fieldsOf.scala

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import language.experimental.namedTuples
2-
31
case class Person(name: String, age: Int)
42

53
type PF = NamedTuple.From[Person]

tests/pos/i20377.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import language.experimental.namedTuples
21
import NamedTuple.{NamedTuple, AnyNamedTuple}
32

43
// Repros for bugs or questions

tests/pos/i21300.scala

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import scala.language.experimental.namedTuples
2-
31
class Test[S <: String & Singleton](name: S):
42

53
type NT = NamedTuple.NamedTuple[(S, "foo"), (Int, Long)]
64
def nt: NT = ???
75

86
type Name = S
9-
7+
108
type NT2 = NamedTuple.NamedTuple[(Name, "foo"), (Int, Long)]
119
def nt2: NT2 = ???
1210

1311
def test =
1412
val foo = new Test("bar")
15-
13+
1614
foo.nt.bar
1715
foo.nt2.bar

tests/pos/named-tuple-combinators.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import scala.language.experimental.namedTuples
21

32
object Test:
43
// original code from issue https://github.com/scala/scala3/issues/20427

tests/pos/named-tuple-selectable.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import scala.language.experimental.namedTuples
21

32
class FromFields extends Selectable:
43
type Fields = (xs: List[Int], poly: [T] => (x: List[T]) => Option[T])

tests/pos/named-tuple-selections.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import scala.language.experimental.namedTuples
21

32
object Test1:
43
// original code from issue https://github.com/scala/scala3/issues/20439

tests/pos/named-tuple-unstable.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import scala.language.experimental.namedTuples
21
import NamedTuple.{AnyNamedTuple, NamedTuple}
32

43
trait Foo extends Selectable:

0 commit comments

Comments
 (0)